- shadowsocks 配置
- Shell脚本编写规范
- A=10
>>># B=$A+10 //10+10
>>># C=”$A+10” //10+10
>>># E=’$A+10’ //$A+10
>>># D=ls
//ls输出的结果
位置参数
位置参数是一种在调用 Shell 程序的命令行中按照各自的位置决定的变量,是在程序名之后输入的参数。 位置参数之间用空格分隔,Shell取第一个位置参数替换程序文件中的 $1,第二个替换 $2 , 依次类推。$0 是一个特殊变量,它的内容是当前这个shell程序的文件名,所以 $0 不是一个位置参数
#! /bin/bash
A=$1
echo $(($A+$2))
B=$(($A+$1))
echo $B
————————————————————-
>>># bash 10 10
20
20
预定义变量
预定义变量和环境变量相类似,也是在Shell一开始就定义的变量,不同的是,用户只能根据shell的定义来使用这些变量,所有预定义变量都是由符号“$”和另一个符号组成。 常见的Shell预定义变量有以下几种。 - ! /bin/bash
echo $1
echo ${2}+${3}
echo $# #打印出位置参数的数量
echo $* #打印出位置参数的内容
echo $? #打印出命令执行后返回的状态
echo $$ #打印出当前进程的进程号
echo $0 #打印出当前进程的进程名 - case 条件判断语句
- for循环语句
- MakeFile
- Gcc 参数
- Linux 程序发布流程
- 写MakeFile
- 编译多级目录
- 特殊含义关键字
- GDB 调试基本命令
- 确定程序是否存在符号表
- 导出符号表文件
- 用Debug程序生成Release 程序
- GDB高级使用方法
- GDB多进程多线程调试
- gdb attach 挂接进程
shadowsocks 配置
1. 安装shadowsocks
sudo apt-get install wget
wget -N —no-check-certificate https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssr.sh && chmod +x ssr.sh && bash ssr.sh
选择 google的BBR加速,依次输入以下代码,并回车即可。
wget —no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh
chmod +x bbr.sh
./bbr.sh
然后重新启动即可
shadowsocks 下载 地址
https://github.com/shadowsocks/shadowsocks-windows/releases
来源
https://ssvpns.com/5%E5%88%86%E9%92%9F%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E7%BF%BB%E5%A2%99ss%E6%9C%8D%E5%8A%A1%E5%99%A8
Shell脚本编写规范
- Shell脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容
一般为:#!/bin/bash 或 #! /bin/sh - 跟在 # 后面的内容表示注释
在shell脚本中尽量不用中文注释,尽量用英文注释 - Shell脚本的命名应以.sh为扩展名
例如:1.sh - 中括号[]两端至少要有1个空格,因此,键入中括号时即留出空格[ ],然后在退格键入中间内容,并确保两端都至少由一个空格
- for循环语句
for
do
内容
done
- if 条件
if 条件内容
then
内容
fi
- Shell 中的变量
变量就是用一个固定的字符串(也可能是字符、数字等的组合)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他内容
变量的赋值方法为: 先写变量名称,紧接着是 “=” ,最后是值。中间无任何空格。 通过echo命令加上 $变量名,即可输出变量的值。 双引号,以防止出错变量的值一般要加上。
定义变量时变量名建议用大写,如 A=simplexue B=99
# 查看变量内容 echo $A 或 echo ${A}
A=str
echo $A
赋值时使用引号的作用
- 双引号:允许通过$符号引用其他变量值
- 单引号:禁止引用其他变量值,$视为普通字符
- 反撇号:命令替换,提取命令执行后的输出结果 全局变量的定义方法 export 变量名
A=10
>>># B=$A+10 //10+10
>>># C=”$A+10” //10+10
>>># E=’$A+10’ //$A+10
>>># D=ls
//ls输出的结果
位置参数
位置参数是一种在调用 Shell 程序的命令行中按照各自的位置决定的变量,是在程序名之后输入的参数。 位置参数之间用空格分隔,Shell取第一个位置参数替换程序文件中的 $1,第二个替换 $2 , 依次类推。$0 是一个特殊变量,它的内容是当前这个shell程序的文件名,所以 $0 不是一个位置参数
#! /bin/bash
A=$1
echo $(($A+$2))
B=$(($A+$1))
echo $B
————————————————————-
>>># bash 10 10
20
20
预定义变量
预定义变量和环境变量相类似,也是在Shell一开始就定义的变量,不同的是,用户只能根据shell的定义来使用这些变量,所有预定义变量都是由符号“$”和另一个符号组成。 常见的Shell预定义变量有以下几种。
- $# :位置参数的数量
- $* :所有位置参数的内容
- $? :命令执行后返回的状态,0表示没有错误,非0表示有错误
- $$ :当前进程的进程号
- $! :后台运行的最后一个进程号
- $0 :当前执行的进程名
! /bin/bash
echo $1
echo ${2}+${3}
echo $# #打印出位置参数的数量
echo $* #打印出位置参数的内容
echo $? #打印出命令执行后返回的状态
echo $$ #打印出当前进程的进程号
echo $0 #打印出当前进程的进程名
Shell脚本的条件测试
条件测试语句 | 说明 |
---|---|
test 测试表达式 | 这是利用test命令进行条件测试表达式的方法 |
[测试表达式] | 这是通过 中括号进行条件测试表达式 和test命令相同 |
[[测试表达式]] | 这是通过 双中括号进行条件测试表达式 和test和[]更新的语法格式 |
&& | 逻辑与 |
|| | 逻辑或 |
expr | 可用与整数运算,也还有其他功能 |
A=”hello”
expr length “$A” //计算A字符串的长度
文件测试操作符
常用文件测试操作符 | 说明 |
---|---|
-d | 文件存在且为目录则为真 |
-f | 文件存在且为文件则为真 |
-e | 文件存在则为真 |
-s | 文件存在且大小不为0则为真 |
-r | 文件存在且可读则为真 |
-w | 文件存在且可写则为真 |
-x | 文件存在且可执行则为真 |
-L | 文件存在且为链接文件则为真 |
f1 -nt f2 | 文件f1比文件f2新则为真 |
12 -ot f2 | 文件f1比文件f2旧则为真 |
字符串测试操作符
常用字符串测试操作符 | 说明 |
---|---|
-n | 若字符串长度不为0,则为真 |
-z | 若字符串长度为0,则为真 |
“字符串1” == “字符串2” | 若字符串1等于字符串2,则为真 |
“字符串1” != “字符串2” | 若字符串1不等于字符串2,则为真 |
注: == 和 != 两端要有空格 ,(())不能用于字符测试
逻辑操作符
在[]和test中使用的操作符 | 在[[]]和(())中使用的操作符 | 说名 |
---|---|---|
-a | && | 与,两端都为真,才为真 |
-o | || | 或, 两端有一个为真,就为真 |
! | ! | 非, 两端相反,则结果为真 |
test 1 == 1 -a 2 == 2
[ 1 == 1 -a 2 == 2 ]
if 条件判断语句
单条件判断##############
if 条件判断
命令
else
命令
fi
#或
if 条件判断;then
命令
else
命令
fi
###双条件判断#####
if 条件判断
then
命令
elif 条件判断
then
命令
else
命令
fi
case 条件判断语句
case 变量 in
[1-9])
命令
;;
[a-z])
命令
;;
*) # 表示最后匹配
命令
esac
for循环语句
for ((i=1;i<=10;i++))
do
echo “循环$i”
done
for ((i=1;i<=10;i++));do
echo “循环$i”
done
while循环语句
i=0
while [$i -lt 10]
do
let i++
echo $i
done
命令 | 含义 |
---|---|
read -p -s | 等代用户输入 -p表示提示语 -s看不见输入内容 |
exit | 退出指令 |
exprot | 创建当前终端下的全局变量 |
PATH | 记录命令执行时的默认是搜索路径 |
read name
echo “输入的是$name”
read a1 a2 a3 a4 //输入多个
read -p “请输入” a1 //就会在提升语后输入 而不是在下一行
read -sp “请输入密码” a1 //就会在提升语后输入 而不是在下一行 而且看不见输入内容
https://blog.csdn.net/qq_36119192/article/details/82964713
MakeFile
- MakeFile 就是 自动化编译脚本
在Linux中把代码编辑成bin (二进制文件) 是使用gcc编译器
在Linux 中 它不关心文件后缀名,但 它的可执行文件后缀名为bin —elf 文件
MakeFile 工作原理
- make / qmake / cmake
在当前文件夹下去寻找makefile / Makefile 的文件去指导gcc去完成编译Gcc 参数
| 参数 | 含义 | | —- | —- | | -c | 对文件进行编译 但是不链接 | | -o | 指定输出文件名 | | -g | 指定 输出文件为 Debug 版本 不加-g自动生成 Release 版本 | | -D | 允许从编译程序命令行定义宏符号 -Ddebug | | -I | 指定第三方头文件位置 | | -L | 指定链接库的搜索目录 -L/usr/lib | | -l(小L) | 指定链接库名字 -lqt | | -O | 指定优化等级 有 -O1 -O2 -O3 不可以特意使用 |
Linux 程序发布流程
- 确定程序是否存在符号表
readelf -s test-1 - 导出符号表
objcopy —only-keep-debug test-1 test-1.symbol - 生成发布版程序
objcopy —strip-debug test-1 test-1-release
strip test-1-release 再一次对发布版去除符号表 - 使用符号表进行程序debug
gdb -q —symbol=test-1.symbol —exec=test-1-release
查看符号表 symbol-file ./test-1.symol
elf生成过程
- 预编译:把.c 和.h 文件 预编译成 .i 文件
- gcc -E -o hello.i hello.c
- 预编译过程中会把:宏定义展开
- 把typedef展开
- 把条件预编译展开
- 把头文件包含到调用文件中去
- 汇编:.i生成 .s文件 .s 是汇编语言
- gcc -S -o hello.s hello.i
- 编译:.s 生成 .o 但还不是可执行文件 还差一步链接
- gcc -c -o hello.o hello.s 必须生成 .o文件
- 链接:生成可执行文件 ELF
- 如果只有一个.c文件是可以不需要链接直接可执行的
- gcc -o hello hello.o
- 生成 hello 文件 用hello.o生成 什么都不加就是链接
写MakeFile
目标文件:依赖文件
命令
makefile 的文件名字必须是 makefile 或 Makefile 不可以是其他的文件名字 也没有后缀名
命令 前面必须有 Tab 键
语法意思是:用这个命令用依赖文件去生成目标文件
但是:生成文件是顺序必须倒过来写 因为当makefile具有递归性,当上面没有找到文件时就会向下去寻找所以:
hello:hello.o
gcc -o hello hello.o
hello.o:hello.s
gcc -c hello.s -o hello.o
hello.s:hello.i
gcc -s hello.i -o hello.s
hello.i:hello.c
gcc -E hello.c -o hello.i
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。。。。。变量
obj = 常量 变量名字按照C的命名规则来
obj := 变量
obj += 追加变量
${obj} 调用变量 {} () 都可以
\ 换行符
# 注释符
objcet := hello.o word.o
cc = gcc
FLAG = -c
hello.o:hello.c
$(cc) $(FLAG) -o hello.o hello.c伪目标
.PHONY: 伪目标标签
filec = hello.o word.o
.PHONY //在这个标签后面的都为伪目标
clean:
rm -rf ${filec}
•~~~~~~~~~~~~
命令行调用
>>> make clean通配符
% 代替一个文件
* 代替所有文件
? 匹配任意一个
.PHONY //在这个标签后面的都为伪目标
clean:
rm -rf *.o //删除所有.o文件隐式规则
隐式规则体现的编译器的智能性,
直接用 .c 也可以生成出 .o文件 因为编译器真的已经帮助我们生成了.i .s文件
只要编译结果名字一样 编译器就可以通过推断找到一样名字的 .c 文件编译自动变量
$@ 代表目标文件
$^ 代表依赖文件
$< 代表第一个依赖文件
cc = gcc
%.o:%.c
$(cc) -c -o $@ $^
通过上面的添加就可以写出很简洁的makefile
————————————————————————————————————————————————————
objcet:= wello.o word.o
cc = gcc
CFLAGS = -g -static
DEFS = —D
FLBS = -L/work/mylib/
TARGET = helloword
$(TARGET):$(object)
$(cc) $(FLAG) $(DEFS) -o %@ %^ $(FLB)
.PHONY
clean:
rm -rf *.o函数
Makefile 中调用函数的方式是 $(函数名 参数)
- 生成 hello 文件 用hello.o生成 什么都不加就是链接
- wildcard 提取当前文件夹下所有参数的文件
SRC = $(wildcard *.c) 提取当前文件夹下所有.c 的文件 - patsubst 字符串替换函数
OBJS = $(patsubst %.c,%.o,$(SRC))
含义是:
$(SRC) 代表内容池
把内容池中所有的 .c 文件替换成 .o 文件
- 修改好后的makefile
简洁 通用 以后使用只需要修改文件名字就行了 单目录版
SRC = $(wildcard .c)
OBJS = $(patsubst %.c,%.o,$(SRC))
cc = gcc
FLAG = -g -static
DEFS =
INCLUDE =
FLBS = -L/work/mylib/
TARGET = helloword
$(TARGET):$(object)
$(cc) $(FLAG) $(DEFS) -o %@ %^ $(FLB)
.PHONY
clean:
rm -rf .o $(TARGET)
变量名含义
FLAG -g 是否为Dubug 版本
LIB -l 库文件
- FIB -L/work/mylib/print.so 指定库路径
编译多级目录
- 使用 makefile.build 脚本进行编译
脚本在 TIM 文件夹中
本程序的Makefile分为3类:
1. 顶层目录的Makefile
2. 顶层目录的Makefile.build
3. 各级子目录的Makefile
一、各级子目录的Makefile:
它最简单,形式如下:
obj-y += file.o
obj-y += subdir/
“obj-y += file.o”表示把当前目录下的file.c编进程序里,
“obj-y += subdir/“表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
注意: “subdir/“中的斜杠”/“不可省略
二、顶层目录的Makefile:
它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数、链接参数──就是文件中用export导出的各变量。
三、顶层目录的Makefile.build:
这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o
详细的讲解请看视频。
四、怎么使用这套Makefile:
1.把顶层Makefile, Makefile.build放入程序的顶层目录
2.修改顶层Makefile
2.1 修改工具链
2.2 修改编译选项、链接选项
2.3 修改obj-y决定顶层目录下哪些文件、哪些子目录被编进程序
2.4 修改TARGET,这是用来指定编译出来的程序的名字
3. 在各一个子目录下都建一个Makefile,形式为:
obj-y += file1.o
obj-y += file2.o
obj-y += subdir1/
obj-y += subdir2/
4. 执行”make”来编译,执行”make clean”来清除,执行”make distclean”来彻底清除
特殊含义关键字
关键字 | 含义 |
---|---|
override | 覆盖变量 |
export | 导出变量到全局 |
override OPENWRT_BUILD=1 //把 OPENWRT_BUILD 变量 重新赋值为1
export OPENWRT_BUILD //把OPENWRT_BUILD 导出到全局
GDB 调试基本命令
目标
- GDB基本命令
- GDB的高级增强插件的使用
- GDB的多线程多进程调试
- GDB破解和破解讲解
安装下载GDB
sudo apt-get install gdb
wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh安装GDB插件GDF
依次运行 即可安装成功
//安装gef 插件
wget -q -O- https://[github.com/hugsy/gef/raw/master/gef.sh](http://github.com/hugsy/gef/raw/master/gef.sh) | sh
wget -q -O ~/.gdbinit-gef.py https://[github.com/hugsy/gef/raw/master/gef.py](http://github.com/hugsy/gef/raw/master/gef.py)
echo source ~/.gdbinit-gef.py >> ~/.gdbinit
//安装peda
git clone https://[github.com/longld/peda.git](http://github.com/longld/peda.git) ~/peda
echo “source ~/peda/peda.py” >> ~/.gdbinit
//安装gdbinit配置信息
git clone git@github.com:gdbinit/Gdbinit.git
cp Gdbinit/gdbinit ~/.gdbinit
启动 GDB
- 想要调试的程序 在gcc 中必须加上 -g | l | 显示函数体内容 | | —- | —- | | b | 给程序下断点 | | info breakpoints | 查看当前程序下的断点 | | run | 运行程序 | | s set | 步进 进入函数中运行调试 | | n nest | 步出 不进入函数中调试 | | p | 查看变量信息 | | u | 跳出当前循环 | | info lpcals | 查看当前函数中所有局部变量的值 | | set listsize | 更换字体大小 | | x / n、f、u | 查看变量地址 | | bt | 查看函数当前函数是谁调用的 | | info frame | 打印栈所有信息 | | shell | 运行shell 命名 | | finish | 结束当前函数 | | disable | 停止断点 | | enable | 启动断点 | | file | 载入符号表 | | set args | 设置 程序进入参数 | | quit | 退出gdb | | display | 每次运行打印信息 | | commands bnum ~~end | 执行到某个断点执行commands bnum中的命令 |
list l
>>> gdb hello //启动gdb
gdb>> l main //显示main 函数内容
gdb>> list 4 //显示主程序前4行代码
gdb>> l hell:4 //显示主程序前4行代码
break
b 18 //在程序18行下断点
break 18 //在程序18行下断点
b 8 if sum>100 //在sum>100时在第8行停下 条件断点
b hello.c:18 if sum>100 //在hello.c文件中的第18行 当sum>100时在第18行停下 条件断点
b heClass::fun //在 heclass类中的fun函数设置断点 针对虚函数
print
x 按十六进制格式显示变量。 d 按十进制格式显示变量。 u 按十六进制格式显示无符号整型。c o 按八进制格式显示变量。 t 按二进制格式显示变量。 a 按十六进制格式显示变量。p/a i c 按字符格式显示变量。p/c i f 按浮点数格式显示变量。
p i //查看i变量的值c
p i=100 //把i的值改为100
p arr@24 //查看arr数组的24个元素
until u
set listsize
gdb>> set listsize20 //设置字体为20
*examine x
符代参数:
参数 | 含c义 | 助记 |
---|---|---|
n | 表示显示内存的长度 | 个数 |
f | 表示显示的格式,跟print 的格式参数相同 | 单位 |
u | 表示从当前地址往后请求的字节数 | 字符 |
u参数可以用下面的字符来代替
b表示单字节,h表示双字节,w表示四字节,g表示八字节
gdb>> x &i //查看i变量的内存地址
gdb>> x/d &i //查看i变量的内容地址的值为十进制
gdb>> x/10xw 0xffe74 //以十六进制查看10个四字节的地址
gdb>> x/100dw x //查看x数组的100个元素 用10进制四字节表示查看
shell
gdb>> shell pwd //在gdcb中运行shell命名
disable / enable c 暂时失效断点
gdb>> info breakpoints //查看当前断点ID号
Num Type Disp Enb Address What
1 breakpoint keep y 0x000055555555464e in main at hello.c:3
breakpoint already hit 1 time
gdb>> disable 1 //取消1号断点
gdb>> enable
gdb>>quit //退出gdb
导入符号表的方法
- 启动后在gdb里面导入
file ./hello.symbol
- 在启动gdb时导入
gdb —symbol-hello.symbol —exec-hello
1.启动后在gdb里面导入
gdb>> file ./hello.symbol
//在当前文件夹下导入hello.symbol 符号表,hello.symbol是用 objcopy —only-keep-debug 生成的
•~~~~~~~~~~~~
2.在启动gdb时导入
shell#>> gdb —symbol-hello.symbol —exec-hello
—symbol 表示符号表文件
—exec 启动文件
set args
启动gdb后设置参数
gdb>> set atgs 10 20 30 40 // 传入 4个参数
gdb>> b 20 // 设置断点在20行出
gdb>> bt //查看当前函数调用栈
display
gdb>> display a //每次运行都打印a的值
gdb>> display 0x1024 //每次运行都打印地址的值
commands bnum ~~end
gdb>> b 20 //设置断点
gdb>> info breakpoints //查看断点id
1 breakpoint keep y 0x00005555555546ea in main at hello.c:15
breakpoint already hit 1 time
gdb>> commands 1 //在1号断点id出设置触发代码
> print a
> shell pwdc
> end //结束输入
geb>> run //运行
确定程序是否存在符号表
readelf -s 确定程序是否存在符号表
readelf -s hello //加上文件名
导出符号表文件
objcopy —only-keep-debug
objcopy —only-keep-debug hello hello.symbol
// 把hello的符号表导出成hello.symbol文件
用Debug程序生成Release 程序
objcopy—strip-debug
objcopy —srtip-debug hello hello.release
// 把Debug版的hello程序脱离符号表生成hello.release程序
GDB高级使用方法
GDB 观察点
是当某一个内存/变量被查看/修改时暂停到这个内存这个地址上
- watch 地址
- info watchpoints
- rwatch
如果在局部变量于全局变量重名 他会按照就近原则来匹配
要观察全局就要加上函数名字和双分号
watch i // 在最近的i变量设置内容访问断点
watch main::i // 在main函数中的全局i变量设置内容访问断点
watch *0x10964 // 在0x10964这个内存上设置设置内容访问断点
捕捉点
可以设置捕捉点来捕捉程序运行时的一些事件,如:载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为:
- catch
| 作用 | 含义 | | | —- | —- | —- | | throw | 一个C++抛出的异常 | 只在C++程序中可以使用 | | catch | 一个C++捕捉到的异常 | 只在C++程序中可以使用 | | exec | 调用系统调用exec时 | exec 表示系统调用 如:在一个进程中启动另一个程序 | | fork | 调用系统调用fork时 | fork 表示创建进程 | | load/load | 载入共享库(动态链接库)时 | load 表示 载入新的库文件时 | | unload 或 unload | 卸载共享库(动态链接库)时 | unload表示 删除载入库时 |
gdb>> throw
gdb>> catch exec
gdb>> load
搜索原代码
命令 | 含义 |
---|---|
reverse-search | 全部搜索 |
forward-search | 向前面搜索 |
search | 向前面搜索 |
forward-search
没有生成的变量 或 还未调用的函数 是搜索不到的
gdb>> b 20 //设置断点
gdb>> run //启动程序 只有进入堆占的 内容才会被搜索到
gdb>> search fun //查找堆占中的fun
GDB多进程多线程调试
- 多进程
进程是一些资源的总和:内存 文件 时间片 协处理器(gpu dsp)
进程是为了运行程序 进程的创建是复制(fork) 完全复制
gid 表示进程 - 多线程
程序执行单元 线程在进程中 是一直轻量级的进程
- 测试调试
程序中生成进程 GDB会默认跟随新的进程执行(子进程)
命令 | 含义 |
---|---|
show follow-fork-mode | 显示当前跟随进程 是 parent or child |
set follow-fork-mode parent | 跟随父进程调试 |
set follow-fork-mode child | 跟随子进程调试 |
set detach-on-fork off | 设置未跟随进程暂停运行 |
info inferiors | 查看当前所有运行进程 带*表示正在查看的进程 |
remmove-inferiors | 删除一个进程 |
add-inferior | 复制一个进程 |
dgb>> set detach-on-fork off //暂停的进程等于已经被杀死 不可以在恢复,可以挂接上去
gdb>> info inferiors //查看当前所有运行进程
gdb>> shell ps -ef | grep 3980 //在系统进程中查找 3980的进程
gdb>> inferior <进程编号> // 跳转到其他进程中去
gdb>> remmove-inferiors <进程编号> //删除一个进程
挂接到另一个进程中去
命令 | 含义 |
---|---|
inferior | 切换到其他进程中去 |
attach | 挂接到另一个进程中 |
gdb>> info inferiors //查看当前所有运行进程
gdb>> attach 3889 //挂接到3889进程上
- attach
gdb>>
gdb>> attach 38891 //挂接到 38891进程中
- add-inferior
add-inferior [-copies n] [-exec executable]
//copies 表示复制进程ID executable 表示新开一个进程 文件路径
add-inferior -copies 3 //复制3号进程
add-inferior -exec ./hello //新开hello程序
gdb attach 挂接进程
启动方式 2 通过进程ID挂接到程序中
shell>> gdb attach 38891 //挂接到38891进程上
gdb>>