C语言揭秘:01 准备工作

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汇编的、编译、链接的相关会渐进式介绍。

  1. makefile

.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镜像文件

  1. QEMU命令

xp /4dw 0xA0000000 //QEMU 命令: 查看0xA0000000出的4个word的10进制数表示
info registers  // 查看寄存器的值
  1. 启动用的汇编代码:startup.s

       .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函数退出后进入死循环
  1. C语言源码:main.c

static int n = 10;    // 作用域为main.c内的整型变量n

int main()  // C语言的入口函数
{
        int n1 = n;
} 
  1. 链接脚本:link.lds

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中要使用*/
}
  1. 运行

执行:
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)]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值