1. c源程序
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello ARM World\n");
return 0;
}
2. 用 gcc 编译成汇编
.arch armv5te
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "hello.c"
.section .rodata
.align 2
.LC0:
.ascii "Hello ARM World\000"
.text
.align 2
.global main
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
sub sp, sp, #8
str r0, [fp, #-8]
str r1, [fp, #-12]
ldr r3, .L3
.LPIC0:
add r3, pc, r3
mov r0, r3
bl puts(PLT)
mov r3, #0
mov r0, r3
sub sp, fp, #4
@ sp needed
ldmfd sp!, {fp, pc}
.L4:
.align 2
.L3:
.word .LC0-(.LPIC0+8)
.size main, .-main
.ident "GCC: (GNU) 4.8.2"
.section .note.GNU-stack,"",%progbits
3. 开始分析
下面是一个
/*
这是一个hello.s arm汇编器原生代码,来源于Android 软件安全于逆向
文件中所有一 .开头都是汇编器指令 ,汇编器指令都是和汇编器相关的
在这里" /*"表示单行注释," \/* *\/ " 其中 \ 是转义符 是多行注释
下面的注释是我看书过程中自己写的,纯属个人理解如果有问题,希望大家一块讨论
*/
.arch armv5te /*处理器架构 指定处理器架构是 armv5te 程序可以在armv5te架构机器上运行*/
.fpu softvfp /*协处理器类型 softvpf 表示使用浮点运算库来模拟协处理器运算 */
.eabi_attribute 20, 1 /*接口属性*/
.eabi_attribute 21, 1 //eabi (embedded application binary interface)
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 18, 4
.file "hello.c" /*源文件名 hello.s 是从hello.c 编译得到的1*/
.section .rodata /*声明只读数据段 相当于 c语言中的常亮 const
这个代码总共有三个段分别是 12 17 44 行 格式是这样的 .section name [,"flags"[,%type[,flag_spefic_arguments]]] */
.align 2 /*对齐方式 2^2=4 字节,数字n 代表是2^n 次方 */
.LC0: /*标号 相当于goto switch 的跳转的标记实际就是代表这一个地址, .ascii 字符串初始地址*/
.ascii "Hello ARM!\000" /*声明 ascII字符串 相当于 声明了一个常亮 const "Hello ARM!\000"*/
.text /*声明代码段 下面就是具体代码*/
.align 2 /*对其方式 和上面的一样 相当于 四个空格 个人觉得*/
.global main /*全局符号 在别的文件中也可以访问 相当于全局变量 或者函数 */
.type main, %function /* .type name ,%nameType 定义name的类型 此处指令是定义main是一个函*/
main: /*标号main 就是地址 */
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
/* stmfd 表示入栈, fd 表示:满递减堆栈.堆栈向低地址生长,堆指针指向最后一个入栈的有效数据项
sp表示的是栈顶 , !是一个可选的后缀,表示最终地址将写回到Rn寄存器
fp 是帧指针,在C程序编译过程中,函数局部变量被分配在一个连续存储区内,这个连续存储区就是该函数的帧
该指令的意思 : fp lr 寄存器 fp处于栈顶,lr是fp-4byte 因为一条指令是 4byte
*/
add fp, sp, #4 sdfsa /* 加法 : fp= sp+4 */
sub sp, sp, #8 /* 减法 : sp= sp-8 */
str r0, [fp, #-8] /* 存储(写)r0寄存器的数据到fp-8所指向的存储单元 */
str r1, [fp, #-12] /* 存储(写)r1寄存器的数据到fp-12所指向的存储单元 */
ldr r3, .L3 /*加载(读)把.L3标记的地址的数据读取到r3寄存器中 即字符串"Hello ARM!\000"的相对偏移量 */
.LPIC0: /* 标记 */
add r3, pc, r3 /* r3=pc+r3 pc是当前指令的地址+r3就是字符串"Hello ARM!\000" 的实际地址*/
mov r0, r3 /* mov 数据传递 相当于赋值 : r0=r3*/
bl puts(PLT)b /* println语句,但是具体看不懂 */
mov r3, #0 /* r3 = 0 */
mov r0, r3 /* r0 = r3 = 0 */
sub sp, fp, #4 /* sp = fp - 4 */
ldmfd sp!, {fp, pc}
/* fp pc 出堆栈 fd 表示:满递减堆栈.堆栈向低地址生长,堆指针指向最后一个入栈的有效数据项
并且把lr 寄存器赋值给pc寄存器 跳出函数 */
.L4:
.align 2
.L3:
.word .LC0-(.LPIC0+8)
/* .word 用来存放地址值 .LC0 是前面定义的一个标号,代表着字符串 Hello ARM !\000 的地址
.LPCIC0 也是标号,代表着add r3, pc, r3 这条指令的地址俩个地址相减就是,俩条指令之间的相对距
离也就是所谓的 相对的偏移量.
+8的问题是我在网上看的:我的理解是肯定是和流水线的关系,但是具体我就不懂了
以下是网上的介绍 : RM立即数在我的印象里最大是4096再大就只能相对寻址,显然所有的指针都只能间接寻址
也就是需要一个内存来存地址那就是L3。。。。L3是个常量,存LPIC0到字符串LC0的相对位置,
只要PC+该相对位置就是字符串位置,因为ARM9是3级流水线一次区取条指令,你当前的PC位置并不是紧接的下一条
而是下3条的位置,通常单片机是1级流水线当前PC就是下条的位置,不用加减任何数,但ARM9需要+8 如果是ARM11的
五级流水线加的更多。ARM不能像单片机那样,想取某个标签地址,就可以 mov r1,#标签 因为ARM立即数寻址有限
制。。。所有的指针都会超过限制所以会用另一种方式直接算出寻址位置的地址和
全局变量位置的相对地址,在调用时用PC+相对地址即可 但要 PC+(相对地址-8), 因为
仪的在减号后面的括号里面所以 按结合律规则 -8编程+8
*/
.size main, .-main
/*.size 表示大小, 符号 . 表示当前指令的地址 .-main 表示的是当前行到main标记的相对偏移量
也就是main函数整个大小*/
.ident "GCC: (GNU) 4.4.3"
/* .ident :编译器标示无实际用途,生成可执行程序后他的值被放置到".comment"段中*/
.section .note.GNU-stack,"",%progbits
/*定义一个.note.GUN-stack段 ,它的作用是禁止生成可执行堆栈, 用
用来保护代码的安bbb全,可执行堆栈常常被用来引发堆栈溢出之类的漏洞*/