C语言揭秘:01 准备工作
子曾经曰过:程序员要对自己所写程序的每个字节都了如指掌。
Talk is cheap,show me the binary code.
by 高尔基
本专栏主要采用的手段是从arm汇编及二进制文件的角度出发,窥探C语言背后的秘密。开始的分析都是裸机代码,没有运行操作系统,所以一个趁手的环境和工具很重要。
本专栏运行测试的环境基于QEMU。关于QEMU可传送到: Welcome to QEMU’s documentation! ‒ QEMU documentation。它是一个可模拟若干处理器架构,及若干硬件平台的模拟器。包括我们要用到的connex(全称Gumstix Connex (PXA255))硬件平台,见https://lucasvr.gobolinux.org/etc/arm/pxa255-developers_manual.pdf,其中的2.12 memory map可关注一下。
- 操作系统:Ubuntu 18.04 LTS (Win10 WSL 也可)
- 需要安装的软件:make, qemu, gcc-arm-none-eabi
- 知识储备:C语言语法基础,makefile基础。
ARM汇编的、编译、链接的相关会渐进式介绍。
.PHONY: all clean run
all::
flash.bin: main.c startup.s link.lds
arm-none-eabi-gcc -nostdlib -o main.elf -T link.lds main.c startup.s
arm-none-eabi-objcopy -O binary main.elf main.bin
dd if=/dev/zero of=$@ bs=4096 count=4096
dd if=main.bin of=$@ bs=4096 conv=notrunc
arm-none-eabi-objdump -hS main.elf > main.dis.s
arm-none-eabi-nm -n main.elf > main.nm #参数n:根据地址从小到大排序
all:: flash.bin
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
run: flash.bin
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
clean:
rm * *.bin *.elf *.dis.s *.o *.nm
执行make flash.bin生成flash镜像文件
xp /4dw 0xA0000000 //QEMU 命令: 查看0xA0000000出的4个word的10进制数表示
info registers // 查看寄存器的值
.section "vectors" @@ 段的名称为vector,该段中的代码就是各个异常向量的入口
reset: b start @@ reset 入口,作为vector段的第一个指令,结合link.lds中vector的起始地址是0x0,
@@根据硬件设计,机器上电马上就执行该指令,接着就马上跳转到start
undef: b undef @@ undefined 异常,为了简化,此处死循环
swi: b swi @@ 死循环
pabt: b pabt @@ 死循环
dabt: b dabt @@ 死循环
nop
irq: b irq @@ 死循环
fiq: b fiq @@ 死循环
.text
start:
@@ Copy data to RAM. 将数据段中的数据从flash中拷贝到RAM中
ldr r0, =flash_sdata @@ r0 = &flash_data, flash_data定义在link.lds中,表示data段在flash中的起始地址
ldr r1, =ram_sdata @@ r1 = &ram_sdata, ram_sdata定义在link.lds中,表示目标位置的起始地址
ldr r2, =data_size @@ r2 = &data_size, data_size定义在link.lds中,表示数据段的大小
@@ Handle data_size == 0.
cmp r2, #0 @@ 比较 data_size和0
beq init_bss @@ 如果相等,也即没有数据要拷贝,就跳转到init_bss
copy: @@ 如果data_size != 0. 就开始拷贝
ldrb r4, [r0], #1 @@ r4 = RAM[r0] ; r0++;
strb r4, [r1], #1 @@ RAM[r1] = r4; r1++;
subs r2, r2, #1 @@ 循环计数器r2 = r2-1; 同时设置条件标志位
bne copy @@ 如果循环计数器值r2不为 0, 跳转到 copy 处继续执行
init_bss: @@ 如果r2为0,执行init_bss,初始化bss段:清零
@@ Initialize .bss
ldr r0, =sbss @@ r0 = &sbss,sbss定义在link.lds中,bss段的起始地址
ldr r1, =ebss @@ r1 = &ebss,ebss定义在link.lds中,bss段的结束地址
ldr r2, =bss_size @@ r2 = &bss_size,bss_size定义在link.lds中,bss段的大小
@@ Handle bss_size == 0
cmp r2, #0 @@ 判断bss_size是否为0
beq init_stack @@ 如果相等就跳转到init_stack继续执行
mov r4, #0 @@ 如果不等,r4=0;
zero: @@ bss段清零
strb r4, [r0], #1 @@ RAM[r0]=r4; r0++;
subs r2, r2, #1 @@ 循环计数器r2=r2-1;
bne zero @@ 如果r2非0,跳转到zero继续执行。
init_stack:
@@ Initialize the stack pointer,为C函数初始化堆栈的栈底,即为sp赋值
ldr sp, =0xA4000000
bl main @@跳转到C的main函数
stop: b stop @@ main函数退出后进入死循环
static int n = 10; // 作用域为main.c内的整型变量n
int main() // C语言的入口函数
{
int n1 = n;
}
SECTIONS { /*elf 文件中的段的布局*/
. = 0x00000000; /*当前地址设置为0x0000000*/
.text : { /*可执行二进制文件的.text段(代码段)*/
* (vectors); /*首先是所以输入文件的vector段,
该例子中只有startup.s中有vector段,
所以vector段的就放在0x00000000处*/
* (.text); /* 紧接在vector段后就是所有文件的.text段,就包括了startup.s和main.c中的.text段 */
}
.rodata : { /*read only data段接在.text段*/
* (.rodata); /*所有输入文件的.rodata段*/
}
flash_sdata = .; /*flash_sdata:flash 中数据段的起始地址,startup.s中要使用*/
. = 0xA0000000; /*当前地址设置为0xA0000000*/
ram_sdata = .; /*ram_sdata: RAM中数据段的其实地址,startup.s中要使用*/
.data : AT (flash_sdata) { /*RAM中的数据段,AT表示该段的加载地址(在flash_sdata处)*/
* (.data); /*所以输入文件的.data段*/
}
ram_edata = .; /*.data段的结束地址,startup.s中要使用*/
data_size = ram_edata - ram_sdata; /*.data段的大小,startup.s中要使用*/
sbss = .; /*.bss段的起始地址,startup.s中要使用*/
.bss : { /*.bss段*/
* (.bss); /*所以输入文件的.bsss段*/
}
ebss = .; /*.bss段的结束地址,startup.s中要使用*/
bss_size = ebss - sbss; /*.bss段的大小,startup.s中要使用*/
}
执行:
make
得到:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b59N91c8-1622283768801)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=N2ZkMDcwMjcyNjA2YWNjODAxNGYyYWFhOWE4Mzc4YTFfSDlSbjludmJoZGxlRVJ6V3lhRG1RNEtZVVFDUm9zSEdfVG9rZW46Ym94Y25mVVlQYXNYM2F1d2JUemV0bHpxamdlXzE2MjIyODExOTI6MTYyMjI4NDc5Ml9WNA)]
此时就进入qemu的仿真环境。输入
xp /6dw 0xa0000000
即可显示地址0xa0000000处的6(/后的数字6)个整形数据(字母w),用十进制显示(字母d)
得到:
[外链图片转存中…(img-b59N91c8-1622283768801)]
此时就进入qemu的仿真环境。输入
xp /6dw 0xa0000000
即可显示地址0xa0000000处的6(/后的数字6)个整形数据(字母w),用十进制显示(字母d)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECWgRitm-1622283768803)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=NjllZDhlNDI1MmRjZTM0NDVhMGMyZGYxNDhlOTViMDZfTFNKMFZoNXBzVUFqMUhFOFBrVzdUaDJsR2c4OWRFbjBfVG9rZW46Ym94Y24xS1IwNVNvdmRYU0pPSzhqMnZGQ21iXzE2MjIyODExOTI6MTYyMjI4NDc5Ml9WNA)]