0%

arm gdb 调试

1. arm64 寄存器和指令

1.1. 寄存器

arm64 有34个寄存器, 包含31个通用寄存器, SP PC CPSR

寄存器中可以存地址, 也可以存值.

寄存器 位数 描述
x0-x30 64 通用寄存器, 可以作 位使用: w0-w30 (访问寄存器的低位)
x0-x7 64 用于子程序调用时参数传递(形参), x0 还用于返回值传递
FP(x29) 64 当前栈帧的栈底 (android上 向上增长), 栈顶地址 < 栈底
LR(x30) 64 程序链接寄存器, 保存子程序结束后需要执行的下一条指令. 即谁调用了当前函数.
SP 64 保存栈指针, 使用SP/WSP来进行对栈寄存器的访问
PC 64 程序计数器, 俗称PC指针, 总是指向即将执行的下一条指令, 在arm64中, 软件不能修改pc寄存器
CPSR 64 状态寄存器, NZCV是状态寄存器的条件标志位,分别代表运算过程中产生的状态

我们可以根据FP和SP寄存器回溯函数调用过程,通过这两个值,我们可以知道函数的栈起始地址(也就是FP寄存器的值), 以及栈顶(也就是SP寄存器的值)。得到了m函数的栈帧,就很容易从里面提取LR寄存器的值了(FP向下偏移8个字节即为LR),也就知道了谁调用了当前函数。以此类推,可以得到一个完整的函数调用链(一般回溯到 main函数或者线程入口函数就没必要继续了)。实际上,回溯过程中我们并不需要知道栈顶SP,只要FP就够了

https://juejin.im/post/6844903923719864333

1.1.1. arm64 约定

  • x0 ~ x7 分别会存放方法的前 8 个参数;如果参数个数超过了8个,多余的参数会存在栈上,新方法会通过栈来读取。
  • 方法的返回值一般都在 x0 上;如果方法返回值是一个较大的数据结构时,结果会存在 x8 执行的地址上。

1.2. 虚存地址与elf 段地虚址

cat /proc/pid/maps 查看用户空间的虚存映射, 栈空间自[stack] 起, 查看pc 值可确定代码当前的运行位置. 程序代码段[.text] 位于maps 的映射的 本elf 文件的映射地址

![[Drawing 2022-04-11 01.04.40.excalidraw]]

1.3. 小端序和大端序

**字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)**。

字节序分为两类:Big-Endian 和 Little-Endian,引用标准的 Big-Endian 和 Little-Endian 的定义如下:

  • Little-Endian:就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  • Big-Endian:就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
  • 网络字节序:TCP/IP各层协议将字节序定义为 Big-Endian(这与主机序相反),因此TCP/IP协议中使用的字节序通常称之为网络字节序。

android 中是小端序, 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端, 所以读字时, 字节间 应该从右往左读, 每个字节的读取还是从左往右

常见CPU字节序

1.4. 寻址空间

android上采用 39位寻址+3级页表

FA36326B-AB85-4E2E-A170-B5091504CECE

1.4.1. 查看elf 各段的地址及确认初始化段区域

1
aarch64-linux-android-objdump -h native_test
  • BSS段 (bss segment)通常是指用来存放程序中未初始化的全局变量初始化为0的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
  • data段 该段用于存储初始化非0的全局变量和静态局部变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段
  • rodata段 常量区,用于存放常量数据,ro就是Read Only之意。但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:
    • 有些立即数与指令编译在一起直接放在代码段。
    • 对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份
    • 用const修饰的全局变量是放入常量区的,但是使用const修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。
    • 有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率
  • text段 text段是用于存放程序代码的,编译时确定,只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起,但不是简单的将它们“堆”在一起就完事,还需要处理各个段之间的函数引用问题。
  • stack段 也就是栈段,常说的堆栈段之一,是由系统负责申请释放,其操作方式类似stack,用于存储参数变量及局部变量,其实函数的执行也是stack的方式,所以才有了递归
  • heap段 也就是俗称的堆,它由用户申请和释放,申请时至少分配虚存,当真正存储数据时才分配相应的实存,释放时也并非立即释放实存,而是可能被重复利用
  • dynamic 段 外部符号表区, gdb调试时可以看到一些符号表信息, 库函数外部可见时, 相关符号表会在该段中
  • sym_tab 段, 外部+内部符号表区, 内部可见(如static 定义的函数), 相关符号在该段中.

1.4.1.1. hexdump打印常量等段的内容

找到基址和偏移 大小后

1
2
3
11 .rodata       00000134  0000000000000dc8  0000000000000dc8  00000dc8  2**2
CONTENTS, ALLOC, LOAD, READONLY, DAT
hexdump native_test -s 0x00000dc8 -n 308 -C

注意一定要使用0x覆盖地址, 不然是打印的10进制的偏移

1.5. arm64 指令

https://wiki.n.miui.com/pages/viewpage.action?pageId=340834589

https://juejin.im/post/6844903816362459144

[]的意思是取地址的值, 不要跟地址弄混了

1.5.1. stp ldp str ldr stur ldur指令

压栈入栈指令, 对于一个64位地址, 将64位地址值压入栈中, 需要有8个字节来存储.一个地址只能存一个字节, 与位长度没有关系 , stp 两个寄存器则需要16个字节. 打印寄存器的值时是按照来打印的, 64位上一个是8个字节

str w9, [sp, #0x8] ; 将寄存器 w9 中的值保存到栈内存 [sp + 0x8] - [sp + 0x8 + 0x4 ] 处

stp x29, x30, [sp, #0x10] ; 将 x29, x30 的值存入 [sp + 0x10] - [sp + 0x10 + 0x10] 字节的位置

stur: 同 str 将寄存器中的值写入到内存中(一般用于 负 地址运算中),如:stur w10, [x29, #-0x4] ; 将寄存器 w10 中的值保存到栈内存 [x29 - 0x04] 处

ldur: 同 ldr 将内存中的值读取到寄存器中(一般用于 负 地址运算中),如:ldur w8, [x29, #-0x4] ; 将栈内存 [x29 - 0x04] 处的值读取到 w8 寄存器中

1.5.2. ldrsw

读取内存地址的值给寄存器。

LDRSW x9 ,[x8 ,x9 ,lsl #2] 为将以x8寄存器为基地址加上x9寄存器向左偏移两个单位的值后取其地址的值给x9寄存器。

1.5.3. adrp

范围寻址, 通俗来讲,ADRP指令就是先进行PC+imm(偏移值)然后找到lable所在的一个4KB的页,然后取得label的基址,再进行偏移去寻址

offset 左移12位(4K对齐), PC的低12位抹0 + offset 4k对齐的结果, 得到一个基址.

1
2
1118:    f0ffffe8     adrp    x8, 0 <abitag-0x2c0>
111c: 91123108 add x8, x8, #0x48c

1.5.4. 指令列表附录

指令 作用
ADC 带进位的位数加法
ADD 位数相加
SUB 位减法
AND 位数的逻辑与
B 在M空间内的相对跳转指令
BR 跳转到某寄存器(的值)指向的地址(无返回), 不会改变 lr (x30) 寄存器的值。
brk 可以理解为跳转指令特殊的一种
BKPT 断点指令
BL 带链接的相对跳转指令-(1) 将下一条指令的地址放入lr(x30)寄存器,(2)转到标号出执行指令
BLX 带链接的切换跳转
BX 切换跳转
BEQ 相等则跳转(Branch if EQual)
BNE 不相等则跳转(Branch if Not Equal)
BGE 大于或等于跳转(Branch if Greater than or Equa)
BGT 大于跳转(Branch if Greater Than)
BIC 位数的逻辑位清零
RET 默认使用lr(x30)寄存器的值通过底层指令提示CPU此处作为下条指令地址(ARM64平台的特色指
BLE 小于或等于跳转(Branch if Less than or Equal)
BLEQ 带链接等于跳转(Branch with Link if EQual)
BLLT 带链接小于跳转(Branch with Link if Less Than)
BLTt 小于跳转(Branch if Less Than)
CDP CDP2 协处理器数据处理操作
CLZ 零计数
CMN 比较两个数的相反数
CMP 位数比较
EOR 位逻辑异或
LDC LDC2 从协处理器取一个或多个位值
LDM 从内存送多个位字到ARM寄存器
STUR 把寄存器的值(位)存到一个内存的虚地址内间(一般等同str)
STR 把寄存器的值(128位/64位)存到一个内存的虚地址内间
LDP str的变种,可以同时操作两个寄存器
LDR(load register) 从内存地址取一个单个的位值加载入通用寄存器
STD ldr的变种,可以同时操作两个寄存器
DLP(load register) 从内存地址取一个单个的64位值加载入通用寄存器
MCR MCR2 MCRR 从寄存器送数据到协处理器
MLA 位乘累加
MOV 传送一个位数到寄存器
MRC MRC2 MRRC 从协处理器传送数据到寄存器
MRS 把状态寄存器的值送到通用寄存器
MSR 把通用寄存器的值传送到状态寄存器
MUL 位乘
MVN 把一个位数的逻辑“非”送到寄存器
ORR 位逻辑或
PLD 预装载提示指令
QADD 有符号位饱和加
QDADD 有符号双位饱和加
QSUB 有符号位饱和减
QDSUB 有符号双位饱和减
RSB 逆向位减法
RSC 带进位的逆向法减法
SBC 带进位的位减法
SMLAxy 有符号乘累加(16位*16位)+位=位
SMLAL 64位有符号乘累加((位*位)+64位=64位)
SMALxy 64位有符号乘累加((位*位)+64位=64位)
SMLAWy 号乘累加((位*16位)>>16位)+位=位
SMULL 64位有符号乘累加(位*位)=64位
SMULxy 有符号乘(16位*16位=位)
SMULWy 有符号乘(位*16位>>16位=位)
STC STC2 从协处理器中把一个或多个位值存到内存
STM 把多个位的寄存器值存放到内存
SWI 软中断
SWP 把一个字或者一个字节和一个寄存器值交换
TEQ 等值测试
TST 位测试
UMLAL 64位无符号乘累加((位*位)+64位=64位)
UMULL 64位无符号乘累加(位*位)=64位
B.LE 标号:小于等于(if判断)
B.LT 标号:小于等于(do while)
B.GT 标号:小于等于(while do)
B.GE 标号:大于等于(for)
B.EQ 标号:比较结果是等于,执行标号,否则不跳转
B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

1.6. gdb调试命令表

1.6.1. 初始化

为了使 gdb 正常工作, 必须使你的程序在编译时包含调试信息,编译时尽量要加上-g参数, 关闭O2优化

1.6.2. 暂停程序

1.6.2.1. 设置断点

break命令(可以简写为b)可以用来在调试的程序中设置断点,该命令有如下四种形式:

  • break line-number 使程序恰好在执行给定行之前停止。

  • break function-name 使程序恰好在进入指定的函数之前停止。

  • break line-or-function if condition 如果condition(条件)是真,程序到达指定行或函数时停止。

    要想设置一个条件断点,可以利用break if命令,如下所示

    1
    2
    break line-or-function if expr
    例:break 46 if testsize==100
  • break routine-name 在指定例程的入口处设置断点

  • break *address 在程序运行的内存地址处停住。

如果该程序是由很多原文件构成的,你可以在各个原文件中设置断点,而不是在当前的原文件中设置断点,其方法如下:

1
2
break filename:line-number
break filename:function-name

从断点继续运行:countinue 命令 c

1.6.2.2. 线程中断

break [linespec] thread [threadno] [if …]
linespec 断点设置所在的源代码的行号。如: test.c:12表示文件为test.c中的第12行设置一个断点。
threadno 线程的ID。是GDB分配的,通过输入info threads来查看正在运行中程序的线程信息。
if … 设置中断条件。

1.6.2.3. 断点管理

显示所有断点信息: i b

  1. 删除指定的某个断点:

    1
    delete breakpoint 1

    该命令将会删除编号为1的断点,如果不带编号参数,将删除所有的断点

    1
    delete breakpoint
  2. 禁止使用某个断点

    1
    disable breakpoint 1

    该命令将禁止断点 1,同时断点信息的 (Enb)域将变为 n

  3. 允许使用某个断点

    1
    enable breakpoint 1

    该命令将允许断点 1,同时断点信息的 (Enb)域将变为 y

  4. 清除原文件中某一代码行上的所有断点

    1
    clean number

    注:number 为原文件的某个代码行的行号

1.6.2.4. 单步调试

  • step 进入的单步执行如果已经进入了某函数,而想退出该函数返回到它的调用函数中,可使用命令finish
  • next 不进入的单步执行
  • ni si 汇编单步执行(不进入 进入)

1.6.2.5. 函数调用

  • call name 调用和执行一个函数

    1
    2
    (gdb) call gen_and_sork( 1234,1,0 )
    (gdb) call printf(“abcd”)$1=4
  • finish 结束执行当前函数,显示其返回值(如果有的话)

1.6.2.6. 添加观察 watcher/ 设置观察点

设置观察点:

  • watch + [变量][表达式] 当变量或表达式值改变时即停住程序。
  • rwatch + [变量][表达式] 当变量或表达式被时,停住程序。
  • awatch + [变量][表达式] 当变量或表达式被读或被写时,停住程序。

1.6.2.7. 设置捕捉点

catch + event 当event发生时,停住程序。event可以是下面的内容:

  • throw 一个C++抛出的异常。(throw为关键字)
  • catch 一个C++捕捉到的异常。(catch为关键字)
  • exec 调用系统调用exec时。(exec为关键字,目前此功能只在HP-UX下有用)
  • fork 调用系统调用fork时。(fork为关键字,目前此功能只在HP-UX下有用)
  • vfork 调用系统调用vfork时。(vfork为关键字,目前此功能只在HP-UX下有用)
  • load 或 load 载入共享库(动态链接库)时。(load为关键字,目前此功能只在HP-UX下有用)
  • unload 或 unload 卸载共享库(动态链接库)时。(unload为关键字,目前此功能只在HP-UX下有用)

1.6.2.8. 捕捉信号

handle + [argu] + signals

  • signals:是Linux/Unix定义的信号

    • SIGINT表示中断字符信号,也就是Ctrl+C的信号,
    • SIGBUS表示硬件故障的信号
    • SIGCHLD表示子进程状态改变信号
    • SIGKILL表示终止程序运行的信号,等等。
  • argu:

    • nostop 当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号
    • stop 当被调试的程序收到信号时,GDB会停住你的程序
    • print 当被调试的程序收到信号时,GDB会显示出一条信息
    • noprint 当被调试的程序收到信号时,GDB不会告诉你收到信号的信息
    • pass or noignore 当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
    • nopass or ignore 当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。

1.6.3. 查看信息

1.6.3.1. 查看值/查看数据

print variable 查看变量
print *array@len 查看数组(array是数组指针,len是需要数据长度)
可以通过添加参数来设置输出格式:

1
2
3
4
5
6
7
8
9
/x 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/s 按字符串格式显示变量 遇到\0 截止
/f 按浮点数格式显示变量。

1.6.2.6. 查看内存地址

examine /n f u + 内存地址(指针变量)
n 表示显示内存长度
f 表示输出格式

1
2
3
4
5
6
7
8
9
/x 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/s 按字符串格式显示变量 遇到\0 截止
/f 按浮点数格式显示变量。

​ u 表示字节数制定(b 单字节;h 双字节;w 四字节;g 八字节;默认为四字节)
​ 如:
​ x /10cw pFilePath (pFilePath为一个字符串指针,指针占4字节)

x 为examine命令的简写

1.6.2.7. 查看栈信息 堆栈

backtrace -n n

n 表示只打印栈顶上n层的栈信息

-n 表示只打印栈底上n层的栈信息。

不加参数,表示打印所有栈信息。

1.6.2.8. 变量的检查和赋值

  • whatis:识别数组或变量的类型
  • ptype:比whatis的功能更强,他可以提供一个结构的定义
  • set variable:将值赋予变量
  • print 除了显示一个变量的值外,还可以用来赋值

1.6.2.9. info

指令 描述
info address Describe where symbol SYM is stored
info all-registers List of all registers and their contents
info args Argument variables of current stack frame
info auxv Display the inferior’s auxiliary vector
info breakpoints Status of user-settable breakpoints
info catch Exceptions that can be caught in the current stack frame
info checkpoints IDs of currently known forks/checkpoints
info classes All Objective-C classes
info common Print out the values contained in a Fortran COMMON block
info copying Conditions for redistributing copies of GDB
info dcache Print information on the dcache performance
info display Expressions to display when program stops
info extensions All filename extensions associated with a source language
info files Names of targets and files being debugged
info float Print the status of the floating point unit
info forks IDs of currently known forks/checkpoints
info frame All about selected stack frame
info functions All function names
info handle What debugger does when program gets various signals
info line Core addresses of the code for a source line
info linkmap Display the inferior’s linkmap
info locals Local variables of current stack frame
info macro Show the definition of MACRO
info mem Memory region attributes
info proc Show /proc process information about any running process
info program Execution status of the program
info registers List of integer registers and their contents
info scope List the variables local to a scope
info selectors All Objective-C selectors
info set Show all GDB settings
info sharedlibrary Status of loaded shared object libraries
info signals What debugger does when program gets various signals
info source Information about the current source file
info sources Source files in the program
info stack Backtrace of the stack
info symbol Describe what symbol is at location ADDR
info target Names of targets and files being debugged
info terminal Print inferior’s saved terminal status
info threads IDs of currently known threads
info tracepoints Status of tracepoints
info types All type names
info variables All global and static variable names
info vector Print the status of the vector unit
info warranty Various kinds of warranty you do not have
info watchpoints Synonym for info breakpoints
info win List of all displayed windows

1.7. 基本gdb命令

命令 简写 功能
file 装入想要调试的可执行文件.
kill k 终止正在调试的程序.
list l 列出产生执行文件的源代码的一部分.
next n 执行一行源代码但不进入函数内部.
step s 执行一行源代码而且进入函数内部.
continue c 继续执行程序,直至下一中断或者程序结束。
run r 执行当前被调试的程序.
quit q 终止 gdb.
watch 使你能监视一个变量的值而不管它何时被改变.
catch 设置捕捉点.
thread t 查看当前运行程序的线程信息.
break b 在代码里设置断点, 这将使程序执行到这里时被挂起.
make 使你能不退出 gdb 就可以重新产生可执行文件.
shell 使你能不离开 gdb 就执行 UNIX shell 命令. CTRL+D退回到gdb
print p 打印数据内容。
examine x 打印内存内容。
backtrace bt 查看函数调用栈的所有信息。