目录
逆向工程原理 L2 编译与二进制文件 I
前言
基于ARM汇编代码进行讨论
内存访问指令
LDR #加载内存
STR #存入内存
如下示例了无内存偏移、有内存偏移以及有无寄存器值改变(intptr++)的不同情况
内存数据对齐
struct
- 结构体整体对齐整体内最长字长
- 字段初始地址满足自身对齐要求:如long对齐8字节,short对齐2字节
例如
struct{
char a;
short b;
long c;
}
假如结构体起始地址为0x0,a大小是1字节,所以a与1字节位置对齐,所以a存放在0x0;
同理,b与2字节位置对齐,所以其起始位置为0x2,注意a与b中间有1字节的空白;c与8字节位置对齐,所以其起始位置为0x8,注意b与c中间有4字节的空白;
最后,整体(结构体)与结构中最长的字长对齐,即8字节,所以结构体的长度为16字节
Union
- 联合的大小为其最长字段大小
虚函数
形式:
virtual int fun(){return 0;}
特点:虚函数是可继承的,并且在子类中可以重载
虚函数表
特点
- 每个实例化对象都持有一个独有的虚函数表(vtable)
- 虚函数表是类的实例共享的
无继承的类
- 虚函数表指针在对象内存开头一字长的位置
- 成员变量内存布局与struct相同
单继承的类
- 从父类中继承的函数在前(有重载则为重载函数),子类新定义的虚函数在后
- 从父类继承的成员变量在虚函数表指针后,在子类成员变量之前
多继承的类
- 按照继承顺序排布虚函数表和成员变量
- 在所有父类数据之后存放子类成员变量
- 子类新定义的虚函数被存放在第一个虚函数表中的最后位置
RTTI机制
RTTI(Run Time Type Information)
使用typeid
运算符来在运行时获取对象类型
type_info信息
可以辅助识别类的结构信息和继承关系
分支语句的机器级表示
跳转语句
B.cond label
BR/BLR Xm #跳转到寄存器存储的地址
BL <address> #函数调用,将next pc保存到LR寄存器
RET(Xm) #若未指定Xm,则将PC设置为LR寄存器内存 储的地址,否则设置为Xm寄存器内的值
CBZ/CBNC X0,label #判断寄存器是否为0
TBZ/TBNZ W0,#20,label #寄存器某一位是否为1
跳转表
当switch语句分支过多时,在内存中会以跳转表的形式进行跳转;
跳转表定义在函数主体之外,当要使用时通过不同case产生不同的offset(偏移),以跳转到不同的分支
寄存器使用规范
一般寄存器
Register | APCS Role |
---|---|
X0 | 返回值 |
X0…X7 | 函数参数 |
X8 | 间接结果地址寄存器 |
X9…X15 | 调用者保存寄存器 |
X16,X17(IP0,IP1) | 函数内暂存寄存器 |
X18 | 平台寄存器 |
X19…X28 | 被调用者保存寄存器 |
X29(FP) | 栈帧寄存器 |
X30(LR) | 链接寄存器 |
SP | 栈顶寄存器 |
浮点寄存器
Register | APCS Role |
---|---|
V0 | 返回值 |
V0…V7 | 函数参数寄存器 |
V8…V15 | 被调用者保存寄存器 |
V16…V31 | 临时寄存器 |
栈空间使用约定
- 当前函数栈空间范围由FP、SP规定
- 函数调用时应当保存FP和LR,并申请函数所用的栈空间
C++函数调用
子类调用父类函数(继承函数)
例如这样的两个类A、B
// test.cpp
class A {
public:
int x;
A() { x = 0; }
virtual void f() { x += 1; }
virtual void g() { x += 2; }
void h() { x += 3; }
} ;
class B: public A {
public:
void f() { x += 5; }
};
int main() {
B* b = new B();
b->h();//调用
return 0;
}
- 需要将当前对象的地址放到第一个参数寄存器,即
this->func(XXX)的调用实现实际上是func(this,xxx)
调用过程
ldr X0,[sp,40] #X0存放对象地址
bl _ZN1A1hEv #调用A类的h方法
C++ 命名方法
原因:C++支持函数重载,所以相同的函数名可能代表不同的方法
普通函数命名
1._Z4funci
2._Z4funcid
int func(int i){} //1.
double func(int i,double d){} //2.
- _Z为GCC标准前缀,4代表该函数名有4个字符
- i代表函数参数类型为int,f和d分别代表参数类型为float和double
- 函数的返回值类型并没有体现在函数名中
复杂函数命名
复杂函数:命名空间内的函数或成员函数
1._ZN3Foo3barEv
2._ZN3Foo3barEd<
class Foo {
public:
int bar() const { //1.
return 123;
}
int bar(double x) { //2.
return x;
}
};
- _Z为GCC标准前缀,3代表该函数名有3个字符
- N代表复杂函数命名,E标志复杂函数命名结束
- 3Foo对应为Foo::
C++调用C函数
C函数中没有C++这种重载命名方法,所以C++调用C函数会出现找不到函数的问题
解决方法
使用extern "C"来在C++代码声明C