用GDB查看任意地址内容
- n, the repeat count
The repeat count is a decimal integer; the default is 1. It specifies how much memory (counting by units u) to display. - f, the display format
The display format is one of the formats used by print,s' (null-terminated string), or
i’ (machine instruction). The default is `x’ (hexadecimal) initially. The default changes each time you use either x or print. - u, the unit size
The unit size is any of
– b Bytes.
– h Halfwords (two bytes).
– w Words (four bytes). This is the
initial default.
– g Giant words (eight bytes).
举例:
n/f/u三个参数可以一起使用。例如:
命令:x/3uh 0x7ff320
表示,从内存地址0x7ff320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十进制显示无符号整型。
GDB打印数据显示格式:
x (hexadecimal)按十六进制格式显示变量。
d (signed decimal)按十进制格式显示变量。
u (unsigned decimal)按十进制格式显示无符号整型。
o (octal)按八进制格式显示变量。
t (binary)按二进制格式显示变量。
a (address)按十六进制格式显示地址,并显示距离前继符号的偏移量(offset)。常用于定位未知地址(变量)。
c (character)按字符格式显示变量。
f (floating)按浮点数格式显示变量。
ARM寄存器
ARM汇编指令集
ARM指令基本格式:
<opcode> {<cond>} {S} <Rd> , <Rn> { , <shift_op2> }
- <>:内项目必选的
- {}:内项目可选的
- cond:条件码,描述指令执行条件
- S:可选后缀,加上“S”,在指令执行完毕后自动更新CPSR中的条件码标志位的值
- Rd:ARM指令中的目标操作数总是一个寄存器,通常用Rd表示
- Rn:存放第1操作数寄存器
- opcode2:第2操作数,不仅可以是寄存器,还能是立即数,而且能够使用经过位移运算的寄存器和立即数。
MOV指令:
指令格式:
MOV {条件}{S} 目的寄存器,源操作数
MOV指令可完成从另一个寄存器、被移位的寄存器或立即数赋值到目的寄存器。其中S选项为指令的操作结果是否操作CPSR中的条件标志位,当没有S选项时指令不更新CPSR中的条件标志位结果。
举例:
MOV R0,R1 ; R0 = R1;
MOV PC,R14 ; PC = R14;
MOV R0,R1,LSL#3 ; R0=R1<<3;
注意:
需要注意的是,这里的立即数是有要求的,不是说任何一个立即数都可以。
要求是:立即数可以由一个8位的常数循环右移偶数位得到。其中循环右移的位数由一个4位二进制的两倍表示。
MOV AL,20H;将8位数据20H传送到AL寄存器
MOV AL,[2000H];将2000H单元的内容传送到AL寄存器 如果加了[
]中括号,就表示括号里面的是地址,传送这个地址里的数据。
如果没有中括号,就直接传送这个数据给目标寄存器
对于不合法的立即数,比如要把0xFFF传送到R1中,虽然不能用MOV指令,但是可以用LDR伪指令,用法是
LDR R1,=0xFFF
load/store指令:用于寄存器和存储器之间数据传送的指令
Load指令用于从内存中读取数据放入寄存器中,
LDR{条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的数据传送到目的寄存器中。当程序计数器PC作为目的寄存器时,指令从存储器中读取的数据被目的地址,从而实现程序流程的跳转。
LDR R0,#8
LDR R0,[R1,R2]
LDR R0,[R1,# 8]
Store指令用于将寄存器中的数据保存到内存。
STR{条件} 源寄存器,<存储器地址>
STR指令用于从源寄存器中将一个32位数据传送到存储器中。
STR #8,R0
STR R0,[R1,#8]
条件码转表
跳转指令
跳转指令用于实现程序流程的跳转,在ARM程序中有两种方法可以实现程序的跳转,一种是使用跳转指令直接跳转,另一种是直接向PC寄存器赋值实现跳转。
- B{条件} 目标地址 B指令最简单的跳转指令,一旦遇到一个B指令,ARM处理器立即跳转至给定的目标地址,由此处继续执行。跳转指令B限制在当前指令的土32MB范围内
- BL{条件} 目标地址 BL是一另个跳转指令,在跳转前会将下一条指令的地址复制到R14中,然后跳转到指定的地址运行程序。可以通过将R14的内容重新加载到PC中,并返回到跳转指令之后的那个指令处执行。
BL是实现子程序调用的一个基本但常用的手段。 (也就**是函数,**最常用的!) - BLX{条件} 目标地址 BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM状态切换到Thumb状态,该指令同时将PC的当前内容到寄存器R14中。当程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现程序的调用和处理器工作状态的切换。子程序的返回可以通过将寄存器R14复制到PC中来完成。
- BX{条件} 目标地址 BX指令是带状态切换的跳转指令,跳转到指定的目标地址执行程序。若目标地址寄存器的[0]位为1,则跳转时自动将CPSR中的标志T置位,即把目标地址的代码解释为Thumb指令。若目标地址寄存器的[0]位为0,则跳转时自动将CPSR中的标志T复位,即把目标地址的代码解释为ARM指令。
举例
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
*pGPFCON = 0x100;
*pGPFDAT = 0;
return 0;
}
e3a03456 mov r3, #1442840576 ; 0x56000000 //把0x56000000放到r3中
e2833050 add r3, r3, #80 (立即数#80是十进制,转为十六进制是0x50) ; 0x50 //把r3的值加80 变成0x56000050
e50b3010 str r3, [fp, #-16] //把0x56000050放到[4076] 第一局部变量
e3a03456 mov r3, #1442840576 ; 0x56000000 //把0x56000000放到r3中
e2833054 add r3, r3, #84 ; 0x54 //把r3的值加84 变成0x56000054
0x56000050放到[4072] 第二局部变量
e51b2010 ldr r2, [fp, #-16] //把内存中[4076]的值0x56000050放到r2中去
e3a03c01 mov r3, #256 ; 0x100
e5823000 str r3, [r2] //把0x100写入[0x56000050]内存中去
e51b2014 ldr r2, [fp, #-20] //把内存中[4072]的值0x56000054放到r2中去
e3a03000 mov r3, #0 ; 0x0
e5823000 str r3, [r2] //把0x0写入[0x56000054]内存中去
nm命令
nm命令主要是用来列出某些文件中的符号(就是一些函数和全局变量等),需要依赖符号表存在,如果符号表删除了就查不到。
执行命令:
nm stack0
查到main地址: 00010434
这个地址与gdb 调试地址是一致的:
gdb stack0
--> disassemble main
objdump 反汇编
可以看到所有区段的反汇编代码和地址,这样我们对照着这个输出信息。
objdump -d stack0 > dump.txt
objdump -D -S stack0 >dump
-D, --disassemble-all Display assembler contents of all sections
-S, --source Intermix source code with disassembly
ARM-栈
栈:栈是一种具有后进先出的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈的数据所处位置,栈顶是最后一个数据进栈所处的位置。
满/空栈
根据SP指针指向的位置,栈可以分为满栈和空栈。
满栈:当堆栈指针总是指向最后压入堆栈的数据
空栈:当堆栈指针总是指向下一个将要放入数据的空位置
ARM采用满栈。
升/降栈
根据SP指针移动的方向,栈可以分为升栈和降栈
升栈:随着数据的入栈,SP指针从低地址->高地址移动
降栈:随着数据的入栈,SP指针从高地址->低地址移动
ARM采用降栈
注:ARM是满降栈
举例:
#include <stdio.h>
int main()
{
...
func1();
...
}
int func1()
{
...
}
例子中有两个函数(包括主函数main),程序运行起来会有一个栈,但这个栈中有两个栈帧。
注意是从高地址到低地址的! (SP由高到低,做sub运算)
fp(r11)栈帧指针,栈帧上边界由fp指针界定,下边界有sp指针界定。从main函数进入到func1函数,main函数的上边界和下边界保存在被它调用的栈帧里面。
- 栈帧(stack frame):就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。
栈帧
(淡蓝色和绿色分别代表两个栈帧,淡蓝色表示已临时压栈保存了关键信息,可以执行被调函数func了,所以FP和SP指向正在执行的当前栈(绿色的),绿色执行完毕后,会返回淡蓝色,从已压栈的PC、LR、SP和FP取值进行操作)
上图描述的是ARM的栈帧布局方式:
-
main stack frame为调用函数的栈帧,func1 stack
frame为当前函数(被调用者)的栈帧,栈底在高地址,栈向下增长。 -
图中FP就是栈基址,它指向函数的栈帧起始地址; SP则是函数的栈指针,它指向栈顶的位置。
-
ARM压栈的顺序依次为当前函数指针PC、返回指针LR、栈指针SP、栈基址FP、传入参数个数及指针、本地变量和临时变量。【这个顺序很重要,自己栈帧的重要信息都保存下来】
-
如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数。
栈的作用
保存局部变量
#include <stdio.h>
int main()
{
int a;
a++;
return a;
}
反汇编:
objdump -D -S stack1 >dump
/*反汇编代码*/
000083a0 <main>:
#include <stdio.h>
int main()
{
83a0: e1a0c00d mov ip, sp
83a4: e92dd800 stmdb sp!, {fp, ip, lr, pc}
83a8: e24cb004 sub fp, ip, #4 ; 0x4
83ac: e24dd004 sub sp, sp, #4 ; 0x4
int a;
a++;
83b0: e51b3010 ldr r3, [fp, #-16]
83b4: e2833001 add r3, r3, #1 ; 0x1
83b8: e50b3010 str r3, [fp, #-16]
return a;
83bc: e51b3010 ldr r3, [fp, #-16]
}
/*分析*/
mov ip,sp //保存sp到ip
stmdb sp!,{fp,ip,lr,pc} /*先对sp-4,再对fp,ip,lr,pc压栈*/
//sp=sp-4;push {pc};sp=pc; /*先压pc*/
//sp=sp-4;push {lr};sp=lr; /*压lr*/
//sp=sp-4;push {ip};sp=ip; /*压ip*/
//sp=sp-4;push {fp};sp=fp; /*压fp*/
sub fp,ip,#4 //fp指向ip-4
sub sp,sp,#4 //开辟一块空间
ldr r3,[fp,#-16] //临时存放在[fp-16]
add r3,r3,#1
str r3,[fp,#-16]
GDB栈显示
(gdb) bt:显示所有栈帧,backtrace。
(gdb) bt 10:显示前面10个栈帧。
(gdb) bt -10:显示后面10个栈帧。
(gdb) bt full:显示栈帧以及局部变量。
(gdb) bt full 10:显示前面10个栈帧以及局部变量。
(gdb) bt full -10:显示后面10个栈帧以及局部变量。
(gdb) frame <栈帧编号>:进入指定的栈帧中,然后可以查看当前栈帧中的局部变量,以及栈帧内容等信息。
(gdb) info frame <栈帧编号>:可以查看指定栈帧的详细信息。
(gdb) up:进入上层栈帧。
(gdb) down:进入下层栈帧。