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 中是小端序, 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端, 所以读字时, 字节间
应该从右往左读
, 每个字节的读取还是从左往右
读
1.4. 寻址空间
android上采用 39位寻址+3级页表
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 | 11 .rodata 00000134 0000000000000dc8 0000000000000dc8 00000dc8 2**2 |
注意一定要使用
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 | 1118: f0ffffe8 adrp x8, 0 <abitag-0x2c0> |
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
2break line-or-function if expr
例:break 46 if testsize==100break routine-name 在指定例程的入口处设置断点
break *address 在程序运行的内存地址处停住。
如果该程序是由很多原文件构成的,你可以在各个原文件中设置断点,而不是在当前的原文件中设置断点,其方法如下:
1 | break filename:line-number |
从断点继续运行: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
delete breakpoint 1
该命令将会删除编号为1的断点,如果不带编号参数,将
删除所有的断点
1
delete breakpoint
禁止使用某个断点
1
disable breakpoint 1
该命令将禁止断点 1,同时断点信息的 (Enb)域将变为 n
允许使用某个断点
1
enable breakpoint 1
该命令将允许断点 1,同时断点信息的 (Enb)域将变为 y
清除原文件中某一代码行上的所有断点
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=4finish 结束执行当前函数,显示其返回值(如果有的话)
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 | /x 按十六进制格式显示变量。 |
1.6.2.6. 查看内存地址
examine /n f u + 内存地址(指针变量)
n 表示显示内存长度
f 表示输出格式
1 | /x 按十六进制格式显示变量。 |
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 |
|
p | 打印数据内容。 | |
examine | x | 打印内存内容。 |
backtrace | bt | 查看函数调用栈的所有信息。 |