2024年1月

一、SQL注入

  • 注入攻击的本质,是把用户输入的数据当做代码执行。这里有两个关键条件,
  • 第一个是用户能够控制输入;
  • 第二个是原本程序要执行的代码,拼接了用户输入的数据。
var sql = "select *  from tableName  where  name='" + "test" + "'";

这个“拼接”的过程很重要,正是这个拼接的过程导致了代码的注入。

如果是一条update/delete语句,就可能会造成严重的后果。

在SQL注入的过程中,如果网站的Web应用程序抛出异常信息,比如攻击者在参数中输入一个单引号“'”,引起执行查询语句的语法错误,而错误信息显示在页面上,对于攻击者来说,构造SQL注入的语句就可以更加得心应手了。

当Web应用程序不显示异常信息,可以使用“盲注”(Blind Injection)的技巧。

二、盲注(BIind Injection)

所谓“盲注”,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的“调试信息”,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到执行。

最常见的盲注验证方法是,构造简单的条件语句,根据返回页面是否发生变化,来判断SQL语句是否得到执行。

比如,一个应用的URL如下:

http://localhost:88/person.aspx?id=2

执行的SQL语句为:

select *  from tableName  where  id=2

如果攻击者构造如下的条件语句:

http://localhost:88/person.aspx?id=2 and 1=2

实际执行的SQL语句就会变成:

select *  from tableName  where  id=2 and 1=2

因为“and 1=2”永远是一个假命题,所以这条SQL语句的“and”条件永远无法成立。对于Web应用来说,也不会将结果返回给用户,攻击者看到的页面结果将为空或者是一个出错页面。

为了进一步确认注入是否存在,攻击者还必须再次验证这个过程。因为一些处理逻辑或安全功能,在攻击者构造异常请求时,也可能会导致页面返回不正常。攻击者继续构造如下请求:

http://localhost:88/person.aspx?id=2 and 1=1

当攻击者构造条件“and 1=1”时,如果页面正常返回了,则说明SQL语句的“and”成功执行,那么就可以判断“id”参数存在SQL注入漏洞了。

在这个攻击过程中,服务器虽然关闭了错误回显,但是攻击者通过简单的条件判断,再对比页面返回结果的差异,就可以判断出SQL注入漏洞是否存在。这就是盲注的工作原理。

Timing Attack

利用 MySQL的BENCHMARK()函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长;通过时间长短的变化,可以判断出注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack。

或者使用微软SQL Server的 waitfor delay '0:0:5' 它用来指定等待的时间。如果将该语句成功注入后,会造成数据库返回记录和Web请求也会响应延迟特定的时间。由于该语句不涉及条件判断等情况,所以容易注入成功。根据Web请求是否有延迟,渗透测试人员就可以判断网站是否存在注入漏洞。同时,由于该语句并不返回特定内容,所以它也是盲注的重要检测方法。

比如构造的攻击参数id值为 :

select * from employee where id=1 if(SUBSTRING(DB_NAME(),1,1)='t') waitfor delay '0:0:5'

这段sql判断数据库名的第一个字母是否为t。如果判断结果为真,则会通过waitfor delay '0:0:5'造成SQL执行延时;如果不为真,则该语句将很快执行完。攻击者遍历所有字母,直到将整个数据库名全部验证完成为止。

数据库攻击技巧

找到SQL注入漏洞,仅仅是一个开始。要实施一次完整的攻击,还有许多事情需要做。将介绍一些具有代表性的SQL注入技巧。了解这些技巧,有助于更深入地理解SQL注入的攻击原理。

常见的攻击技巧

比如构造的攻击参数id值为以下,则是利用union select来分别确认表名admin是否存在,列名passwd是否存在:

select * from employee where id=1 union all select 1 from amdin
select * from employee where id=1 union all select 1,password from amdin

进一步,想要猜解出username和password具体的值,可以通过判断字符的范围,一步步读出来:

select *from employee where id=1 IF ASCII(SUBSTRING((select top 1 city from Customers),1,1))=49 WAITFOR DELAY '0:0:5'

这个过程非常的烦琐,所以非常有必要使用一个自动化工具来帮助完成整个过程。sqlmap.py就是一个非常好的自动化注入工具。

命令执行

在MS SQL Server中,则可以直接使用存储过程“xp_cmdshell”执行系统命令。

攻击存储过程

在MS SQL Server中,存储过程“xp_cmdshell”可谓是臭名昭著了,无数的黑客教程在讲到注入SQL Server时都是使用它执行系统命令:

exec master.dbo.xp_cmdshell 'cmd.exe dir c:'
exec master.dbo.xp_cmdshell 'ping'

除了xp_cmdshell外,还有一些其他的存储过程对攻击过程也是有帮助的。
比如

  • xp_regread可以操作注册表;
  • xp_servicecontrol,允许用户启动、停止服务;
  • xp_terminate_process,提供进程的进程ID,终止此进程。
  • xp_ntsec_enumdomains,列举服务器可以进入的域。
  • xp_loginconfig,获取服务器安全信息。
  • xp_dirtree,允许获得一个目录树。

SQL自动注入工具

学习了SQL注入的相关知识和技术,当发现有SQL注入漏洞的时候,一般都需要发送大量的请求以便从Web应用程序后台的远程数据库中获取需要的信息,这种手动检测方法费时且效率较低,一些专门的软件可以帮助我们进行检测,正确运行这些软件只需要根据界面提示进行相关操作就可以了。这些软件主要有:

正确地防御SQL注入

从防御的角度来看,要做的事情有两件:

  • (1)找到所有的SQL注入漏洞;
  • (2)修补这些漏洞。

SQL注入的代码层防御:在编写Web应用程序时应该如何进行代码的防御

输入验证防御(JS前端)

输入验证是指在Web页面代码中,用户提交表单数据前,利用一定的规则对输入的数据进行合法性验证。这里的验证不仅要验证数据的类型,还应该利用正则表达式或业务逻辑来验证数据的内容是否符合要求。

Web服务器端数据验证

1、ASP.NET应用程序通过System.Web.HttpRequest类获取用户提交的输入。这个类中包含大量Web应用程序用于访问用户提交的数据的属性和方法。如下表所示:

收到的参数主要有4类,分别是Form参数、URL参数、Cookies参数和Session参数。例如在Web服务器端通过ASP获取这些参数的语句如下。

  • ① Form参数的读取:UserName= Request.Form["User1"];
  • ② URL参数的读取:UserName= Request.QueryString["User1"];
  • ③ Cookies参数读取:UserName=Request.Cookies["User1"];
  • ④ Session参数读取:UserName=Session["User1"];

检查输入数据的数据类型。比如输入时间、日期时,必须严格按照时间、日期的格式,等等,都能避免用户数据造成破坏。但数据类型检查并非万能,如果需求就是需要用户提交字符串,比如一段短文,则需要依赖其他的方法防范SQL注入。

通过代码过滤防御:过滤关键词
"xp_cmdshell","truncate","dump","net user","--","/*"
,"delete","update","insert","exec","count("
,"RESTORE","net localgroup","asc","execute","desc"
,"drop","truncate","char","grant","master","netlocalgroup administrators"

还有大量应用程序允许用户指定排序关键字(ASC或DESC)

正确地防御SQL注入:

  • 1、参数化参数
  • 2、使用ORM框架查询

欢迎关注公众号:

一、前言

大家好,这里是白泽。有一些同学提问,希望在自己的简历上增加一些有含金量的项目经历,最好能够去参与一些开源项目的开发,但由于对一个庞大的开源项目缺乏认知,难以着手。同时也担心自己能力不足,不知道自己写的代码是否会被接纳。

这里我总结了遇到的一些问题:

  1. 如何找到适合我自己的开源项目?白泽你能帮帮我吗!
  2. 确定了项目,在项目中如何找到自己能做的需求?白泽你能帮帮我吗!
  3. 找到了需求,如何阅读开源项目的代码?白泽你能帮帮我吗!
  4. 写完了代码,提交代码的流程是什么样的?白泽你能帮帮我吗!

必须能!



耐心看完这篇文章,你会发现其实参与开源并不遥不可及。

在文章的后半部分,白泽将分享自己在不同社区参与的一些开源和之前在
Gitea
(GitHub

大家好,我是呼噜噜,最近在看linux早期内核
0.12
的源码,突然想到一个困扰自己好久的问题:当我们按下电源键,计算机发生了什么?神秘地址0x7C00究竟是什么?操作系统又是如何被加载到硬件中的?带着这些问题,继续往下阅读本文。

x86计算机启动过程,主要分为这几个阶段:BIOS自检,引导设备的选择,主引导记录,加载操作系统

BIOS自检

当电源自检通过后,主板会通电,开始读取
ROM里面的BIOS程序
,进行
BIOS自检

BIOS
(Basic Input Output System,基本输入输出系统),它负责
管理和控制计算机硬件设备
,本质上是一组"程序代码"。

BIOS作为计算机开机之后,CPU要进行处理的第一个“可执行程序”,也就是第一个“开机启动项”。其程序代码事先被刷入集成在主板的
ROM
芯片中,主要保存着系统设置程序、基本输入输出程序、开机上电自检程序和系统启动自举程序等

ROM:
只读存储器,不需要供电也可保持数据不丢失
。这点特性和内存有着鲜明的对比

现在一般用
Flash闪存
来代替ROM,由于ROM写入后就不能修改,改用Flash闪存后,既方便又能用专用程序即可修改其中代码

当BIOS启动后,会开始BIOS自检,会依次执行以下操作:

  1. 主要是对计算机的硬件设备进行检测,然后初始化,包括处理器、内存、硬盘、显卡、声卡等
  2. 对计算机内存进行检测,检查硬盘和其他存储设备是否正常
  3. BIOS还会检查计算机的其他设备,包括键盘、鼠标、显示器、声卡等
  4. 显示系统信息:BIOS会在屏幕上显示系统信息,包括计算机型号、处理器型号、内存大小、硬盘容量等

作者:小牛呼噜噜 ,首发于公众号「
小牛呼噜噜

期间如果检测到设备或者硬件运行不正常,主板会发出不同的蜂鸣声(具体查看
bios手册
区分)来作为警告,同时启动会中止。同时我们不用担心检测时间过长,往往我们还没感觉的时候,计算机的BIOS自检就已经完成了。

引导设备选择

当BIOS完成硬件自检后,需要进行引导设备的选择,主要是
确定计算机从哪个设备启动
,通常是硬盘或可移动设备。这一步也是我们常说的
BIOS下的启动顺序
,计算机需要知道下一个阶段要启动的程序具体放在了哪一个设备上。

用户可以自行去选择从哪个设备启动计算机,比如光盘或USB驱动器,进而调节启动设备的优先级。

BIOS操作界面,有一项就是"设定启动顺序",我们一般使用U盘装系统就得在这一步设置为U盘启动。

主引导记录

当BIOS按照我们设定好的启动顺序,BIOS则根据这个顺序
将计算机的控制权交给排在第一位的存储设备
。主要目的是
将硬盘中操作系统的核心代码加载到计算机的内存中并执行它
。通俗点讲,主引导程序它个头比较小,操作系统个头大,它的任务就是去加载一个个头大的程序,也就是操作系统。

那怎样将操作系统的核心代码加载到计算机的内存中并执行?我们只需将从
第一位设备
中读取设备的
MBR
,并且将程序放在
0x7c000
的内存地址位中即可

MBR是什么?

MBR是存储设备中的
第一个扇区
,也就是磁盘最前面的512字节(Byte),称为“
主引导记录
”(Master boot record,简称为MBR)

我们这就不得不提机械硬盘的构造了,常见的机械硬盘的组成部分:


我们就不一一介绍了,我们直接讲其存储数据的原理。机械硬盘存储数据的时候,是将数据存储在其内部的
盘面
上。一块硬盘中一般会有多个盘片。

盘片的表面涂有
磁性物质
,这些磁性物质用来
记录二进制数据
。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。

每个盘面都会有对应的磁头,所有磁头"共进共退",因为会有一个磁臂固定它们。

每个盘面划分成了一圈一圈的
磁道
,最外圈是0磁盘。然后每个磁盘又划分为了一个个小块,小块叫做
扇区

扇区大小固定,是512个字节

扇区大小为啥固定512字节?这是1956年由industry trade organization, International Disk Drive Equipment和Materials Association三家机构确定的行业标准, 大家都默认会遵守的标准,久而久之,如果改变的话,兼容各种情况的代价非常大。

我们现在更常用的固态硬盘的原理是和机械硬盘是截然不同的,大家感兴趣地可以自行去了解。上面科普了许多磁盘的知识,下面让我们回到MBR上。

由于BIOS很小,功能有限,为了完成加载操作系统的功能,就诞生了MBR

MBR在** 0 盘 0 道0扇区
上(最外层磁盘的最外围磁道的第一个扇区),也就是该储存设备的第一个扇区(大小512个字节)


存放了用于启动操作系统的引导程序代码
,其实这串代码就是
告诉计算机去哪一个地址去找操作系统**。

主引导记录由三个部分组成:

  1. 占446个字节:**
    主引导程序(也叫
    Boot Loader
    ),如果启动管理器grub是直接写进mbr硬盘的主引导记录中的,计算机BIOS 在启动时,按照预定的方式,
    将MBR内的代码加载至内存指定位置**, 然后跳转至那里,mbr的代码就开始运行了。
  2. 占64个字节:记录分区表,由于硬盘可以分区,每个区可以安装不同的操作系统,所以主引导记录必须知道将控制权转交给哪个区。
  3. 占2个字节:主引导记录的签名(0x55和0xAA),如果这512字节的最后两个字节是0x55和0xAA,表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给"启动顺序"中的下一个设备。如果到最后还是没找到符合条件的,直接报出一个无启动区的error。

额外补充一下,分区表的概念:
分区表
的长度只有64个字节,里面又分成四项,每项16个字节。所以,一个硬盘最多只能分四个一级分区,又叫做"主分区"。

每个主分区的16个字节,由6个部分组成:

  • 第1个字节:如果为0x80,就表示该主分区是激活分区,控制权要转交给这个分区。四个主分区里面只能有一个是激活的。
  • 第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)。
  • 第5个字节:主分区类型。
  • 第6-8个字节:主分区最后一个扇区的物理位置。
  • 第9-12字节:该主分区第一个扇区的逻辑地址。
  • 第13-16字节:主分区的扇区总数,决定了这个主分区的长度,一个主分区的扇区总数最多不超过2的32次方。。

当主引导程序将硬盘的第一个扇区中读取到的
操作系统引导程序代码
(512 个字节的内容),加载(原封不动地复制)到计算机内存
0x7c00
这个位置。这个过程需要依靠硬盘控制器和指令集来完成。

那看到这里大家一定有一个疑惑?计算机为啥要把操作系统代码加载到内存地址
0x7c00处
,其他地址不行吗?

0x7c000是什么神秘的地址?

0x7C00这个地址,它不属于Intel x86平台规范的,你翻遍Intel x86的手册,也找不到它的定义。它其实是历史遗留原因,第一次出现于IBM PC 5150电脑,IBM PC 5150是x86(32位)IBM PC/AT系列的老祖宗,其使用了intel 8088芯片,后人一直沿用这个地址以保持兼容。

那这个地址是怎么得出来的呢?IBM PC 5150电脑的操作系统是
86-DOS
,它最少需要32KB的内存,要知道那个时候内存是非常宝贵的,能省就省,勤俭持家嘛。

32KB=32 * 1024 B = 32768 B = 0x8000 B
, 由于内存地址是从0x0000开始的,32位末地址是
0x8000 -1=0x7FFF
所以32KB的内存地址是
0x0000~0x7FFF

现在又知主引导记录需要512字节,其本身引导程序的堆栈/数据区域也至少需要512 字节

0x7FFF -512 -512 + 1=0x7C00
,这样就得出
0x7C00
这个地址了。

计算机启动会后,内存布局如下:

+——————— 0×0
| Interrupts vectors(中断向量表)
+——————— 0×400
| BIOS data area(BIOS的数据区域)
+——————— 0×5??
| OS load area(操作系统加载区域)
+——————— 0x7C00
| Boot sector(引导区域)
+——————— 0x7E00
| Boot data/stack(引导数据/堆栈)
+——————— 0x7FFF
| (not used) 
+——————— (…)

当操作系统启动后,主引导记录也就完成使命了,其所在的内存地址
0x7C00
之后的所有内存,皆可被操作系统重新利用,突出一个节约内存的理念。

加载操作系统

操作系统的加载是计算机启动过程中的最后一步,控制权将被转交给操作系统,主要工作是操作系统的所有模块加载到内存中,完成操作系统的初始化,最终实现**

我们这里以Linux为例,首先会去加载boot目录下的内核启动文件,成功加载内核后,然后去启动init进程(pid进程号为1),最后去加载操作系统的各个模块,比如网络、IO设备、窗口等,使得操作系统能够正常运行。

这块就不细讲了,如果去深扒linux的源代码,远远不是一篇文章能讲完的。后面笔者会尝试去更新linux0.12的相关文章,敬请期待

直到计算机中出现我们所熟悉,输入用户名密码登录的界面


参考资料:
《Linux内核完全注释5.0》
https://www.minitool.com/lib/power-on-self-test.html
https://www.ruanyifeng.com/blog/2015/09/0x7c00.html
https://www.glamenv-septzen.net/en/view/6


作者:小牛呼噜噜 ,首发于公众号「
小牛呼噜噜

感谢阅读,
原创不易
,如果有收获的话,就点个免费的[

]or[
转发
],你的支持会激励我输出更高质量的文章,感谢!

前言

日常开发时有些业务场景功能很复杂,如果将所有代码都写在一个vue组件中,那个vue文件的代码量可能就几千行了,维护极其困难。这时我们就需要将其拆分为多个组件,拆完组件后就需要在不同组件间共享数据和业务逻辑。有的小伙伴会选择将数据和业务逻辑都放到
pinia
中,这样虽然可以解决问题。但是如果将所有的复杂的业务都放在
pinia
中,那么
pinia
就会变得很乱。

将数据和业务逻辑都封装到
hooks

这时你还有另外一个选择,使用
Composition API
将数据和业务逻辑都抽取到
hooks
中。
state
状态的定义和更新以及具体的业务逻辑全部由
hooks
内部维护,组件只负责使用
hooks
暴露出的
state
状态和方法。

下面是我们封装的
hooks

export const useStore = () => {
  const count = ref(0);

  const doubleCount = computed(() => {
    return count.value * 2;
  });

  function increment() {
    count.value = count.value + 1;
  }

  function decrement() {
    count.value = count.value - 1;
  }

  return {
    count,
    doubleCount,
    increment,
    decrement,
  };
};

组件只需要使用hooks中暴露出的状态
count

doubleCount
,以及方法
increment

decrement
,无需关注具体的内部逻辑是如何实现的。

上面的封装其实是有问题的,如果我们将组件拆为两个,分别为
CountValue.vue
(显示
count
的值)和
CountBtn.vue
(修改count变量值)。

CountValue.vue
组件代码如下:

<template>
  <p>count的值是{{ count }}</p>
  <p>doubleCount的值是{{ doubleCount }}</p>
</template>

<script setup lang="ts">
import { useStore } from "./store";

const { count, doubleCount } = useStore();
</script>

CountBtn.vue
组件代码如下:

<template>
  <button @click="decrement">count--</button>
  <button @click="increment">count++</button>
</template>

<script setup lang="ts">
import { useStore } from "./store";

const { decrement, increment } = useStore();
</script>

由于我们的
count
变量是在
useStore
函数中定义的,所以每调用一次
useStore
函数都会重新定义一个
count
变量。在我们这里
CountValue

CountBtn
组件都在
setup
中调用了
useStore
函数,通过
useStore
函数拿到的就不是同一个
count
变量。这样就会导致我们在
CountBtn
中修改了
count
变量的值,但是
CountValue
组件中显示的
count
变量的值一直没变。

多个组件同时调用
hooks
如何共享同一份state状态

要解决上面的问题其实很简单,问题的原因是因为每次调用
useStore
函数都会生成一个新的
count
变量。那我们就不将
count
变量的定义写在
useStore
函数中,只需要将
count
变量的定义写在
useStore
函数的外面就可以了。

下面是优化后的
hooks

import { computed, ref } from "vue";

// 将count的定义放在外面
let count;
export const useStore = () => {
  if (!count) {
    count = ref(0);
  }

  const doubleCount = computed(() => {
    return count.value * 2;
  });

  function increment() {
    count.value = count.value + 1;
  }

  function decrement() {
    count.value = count.value - 1;
  }

  return {
    count,
    doubleCount,
    increment,
    decrement,
  };
};

我们将
count
变量定义放在了
useStore
的外面,并且只有第一次调用
useStore

count
的值为空才会执行
count = ref(0)
。后面再次调用
useStore
时由于
count
已经被
ref
赋值为一个对象了,所以就不会再次走
if
逻辑。这样
CountValue

CountBtn
组件中调用
useStore
拿到的
count
变量都是我们在
useStore
函数外面定义的了。

那么这里的计算属性
doubleCount
为什么不放在
useStore
外面定义也可以呢?因为计算属性
doubleCount
的值是由
count
变量计算得来的,所以我们只需要保证每次调用
useStore
时访问的
count
变量是同一个,那么
doubleCount
计算属性的值就是相同的。当然你也可以将计算属性
doubleCount
的定义也放在
useStore
外面。

总结

这篇文章介绍了在多个组件中需要复用状态和业务逻辑的情况时,我们可以不将这些状态和业务逻辑写到
pinia
中,而是使用
Composition API
将状态和业务逻辑封装成一个
hooks
。为了多个组件同时调用
hooks
时能够共用同一个
state
状态,我们需要将定义的
ref
变量写在
useStore
函数外面。

如果我的文章对你有点帮助,欢迎关注公众号:【欧阳码农】,文章在公众号首发。你的支持就是我创作的最大动力,感谢感谢!

vscode的配置文件

总述:vscode中一般会在项目文件夹下自动生成.vscode文件夹,其中存放若干配置文件(.json),一般有如下文件:

image-20240123150446630

下面将解释每个文件的用途与表现。

1. c_cpp_propertries.json

这个文件是使用vscode进行C++开发时会产生的文件,非C++用户可以直接跳过。其内容大致如下:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/include/python3.8",
                "/data/anaconda_new/envs/cugr/lib/python3.8/site-packages/torch/include",
                "/data/anaconda_new/envs/cugr/lib/python3.8/site-packages/torch/include/torch/csrc/api/include",
                "/home/ustc/globalRouting/routing2/Xplace-main/cpp_to_py",
                "${workspaceFolder}/cpp_to_py"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c17",
            "intelliSenseMode": "linux-clang-x64"
        }
    ],
    "version": 4
}

可以看出,这个文件通常指定CPP项目本身的一些属性,比如使用什么C++标准进行预编译(
cStandard
),includePath路径是什么(
#include的搜索位置
),当然这些配置只是为了在编辑器中正常显示,不报错之类的。如果我们不使用vscode本身的编译环境编译,比如使用cmake或者自定义C++命令编译,那么就可以不配置这些东西,只是可能会在编辑器界面上显示一堆的红色黄色波浪线。

2. launch.json

这个文件是用于启动程序(run/debug)时会查看的配置文件,比如启动程序时传递参数(当然如果直接命令行传递就没必要写)。示例内容如下:

{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "route",
        "type": "cppdbg",
        "request": "launch",
        "program": "${workspaceFolder}/route",
        "args": [
            "-cap",
            "data/ISPD/ariane133_68.cap",
            "-net",
            "data/ISPD/ariane133_68.net",
            "-out",
            "test.output"
        ],
        "stopAtEntry": false,
        "cwd": "${workspaceFolder}",
        "environment": [],
        "externalConsole": false,
        "MIMode": "gdb",
        "setupCommands": [
          {
            "description": "Enable pretty-printing for gdb",
            "text": "-enable-pretty-printing",
            "ignoreFailures": true
          }
        ],
        "preLaunchTask": ""
        // "postDebugTask": "install"
      }
    ]
  }
  

在这个示例文件中,比较重要的是name,type,program,args,preLaunchTask等,name用于指定这个launch的名字,这会在vscode的运行调试界面的下拉框中显示,比如这里命名为route,我的vscode中就有显示如下:
image-20240123151513505

program
是指定运行的程序名,
preLaunchTask
是运行前提前执行的命令,这里为空,表示不执行任何命令,或者也可以设置为"build",表示运行前先进行重新编译。如果你的项目文件非常大,那么每次调试都要花费漫长的时间进行重新编译,这显然是很浪费时间的,所以可以将此字段设置为空(即使使用的是cmake tools,在这里也可以设置,因为cmake tools也会来匹配相应的配置,后续详细说明)。

3. tasks.json

这个配置文件中定义了自定义的一些任务,但应该会有C++编译器自动生成的任务如下,即调用g++进行编译:

{
    "type": "cppbuild",
    "label": "C/C++: g++ build active file",
    "command": "/usr/bin/g++",
    "args": [
        "-fdiagnostics-color=always",
        "-g",
        "${file}",
        "-o",
        "${fileDirname}/${fileBasenameNoExtension}"
    ],
    "options": {
        "cwd": "${fileDirname}"
    },
    "problemMatcher": [
        "$gcc"
    ],
    "group": {
        "kind": "build",
        "isDefault": true
    },
    "detail": "Task generated by Debugger."
}

当然我们也可以额外进行定义一些自己的任务,比如每次cmake之后我都需要执行make install任务,我可以将这个任务写在tasks.json中,全部文件内容如下:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        },
        {
            "type": "shell",
            "label": "install",
            "command": "make install",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ],
    "version": "2.0.0"
}

这样里面就有两个任务,
名字(label)
分别为:

  • C/C++: g++ build active file

  • install

按Ctrl+shift+P,输入Tasks:Run Task,会显示出这两个任务:
image-20240123152431331

点击install就会执行这个make install任务了,不用再另外打开终端输入命令了(尤其命令行很长的话)。

当然这个也可以和前面的launch.json相互配合使用,比如我可以指定"postDebugTask": "install"(倒数第四行),这样就会在执行完程序后自动执行make install命令(只是举例,比如你可以写一个命令将程序执行输出的文件挪到固定的一个位置)。

4. setting.json

这个文件通常是用于描述一些插件的设置,或者编辑器本身的设置。只适用于这个项目喔~

{
    "cmake.debugConfig": {
        "args": [
            "-cap",
            "data/ISPD/ariane133_68.cap",
            "-net",
            "data/ISPD/ariane133_68.net",
            "-out",
            "test.output"
        ]
    }
}

比如在这个文件中,我仅设置了cmake tools的传递参数配置。在这里读者可能发现我在launch.json中也配置了cmake的执行传递参数。cmake会先去匹配launch.json中是否有对应的配置(查看program是否与当前执行的文件匹配),如果有则使用此配置,如果没有则搜索setting.json的"cmake.debugConfig"字段匹配。

总结:

这些是我尝试在vscode中使用cmake进行简单化的调试大项目总结的一些关于配置文件的东西,之前没有关注过这些,希望总结出来也能方便大家的使用!感谢阅读~