05 printf函数可变参数的实现原理之汇编分析


如实现一个像printf函数格式的函数:
test.c
   void myprintf(char *line, ...) // line指针变量是局部变量,在栈里分配空间
   {
    printf(line); //调用printf时,r0存放字符串地址
   }

   int main(void)
   {
      myprintf("hello test %d, %d, %s, %d, %c\n", 11, 22, "nono", 7788, 'K');
      //调用myprintf时,共有6个参数, r0存放字符串地址, r1存11, r2存22, r3存放"nono"字符串地址, 后面两个参数需存入栈里

      return 0;
   }

在myprintf函数是如何知道参数的个数及如何取出参数的值?
调用myprintf函数时的第一个参数是一个字符串的地址,通过遍历字符串里的’%’字符,可以得知后面还带有多少个参数。
参数值的获取只能通过汇编来看了。

看汇编代码前,先回顾下:
在arm程序里,参数是从r0寄存器开始传参数,直到r3寄存器存放要传递的第四个参数,再多的参数就需压栈了。
函数的参数也是局部变量在栈里分配空间。

反汇编得到的代码:

000083f4 <main>:
    83f4:       e92d4800        push    {fp, lr}
    83f8:       e28db004        add     fp, sp, #4
    83fc:       e24dd008        sub     sp, sp, #8
    8400:       e59f302c        ldr     r3, [pc, #44]   ; 8434 <main+0x40> //取出7788的值存入寄存器r3
    8404:       e58d3000        str     r3, [sp]          //把寄存器r3里的值(7788)压栈里
    8408:       e3a0304b        mov     r3, #75 ; 0x4b    // 字符'K'的ascii值存入寄存器r3
    840c:       e58d3004        str     r3, [sp, #4]      // 再把寄存器r3里存放的值压栈, 注意位置是(sp+4)
    8410:       e59f0020        ldr     r0, [pc, #32]   ; 8438 <main+0x44>  //字符串地址存入寄存器r0
    8414:       e3a0100b        mov     r1, #11          // r1存入11
    8418:       e3a02016        mov     r2, #22          // r2存入22
    841c:       e59f3018        ldr     r3, [pc, #24]   ; 843c <main+0x48>  //把"nono"字符串的地址存入寄存器r3
    8420:       ebffffea        bl      83d0 <myprintf>  //调用myprintf
    8424:       e3a03000        mov     r3, #0
    8428:       e1a00003        mov     r0, r3
    842c:       e24bd004        sub     sp, fp, #4
    8430:       e8bd8800        pop     {fp, pc}
    8434:       00001e6c        andeq   r1, r0, ip, ror #28   // 7788的十六进制值存放在此
    8438:       00008494        muleq   r0, r4, r4  
    843c:       000084b4       // "nono"字符串的地址是0x84b4

000083d0 <myprintf>:
    83d0:       e92d000f        push    {r0, r1, r2, r3}
    83d4:       e92d4800        push    {fp, lr}
    83d8:       e28db004        add     fp, sp, #4  
    83dc:       e59b0004        ldr     r0, [fp, #4]   //r0存放 line指针变量指向的地址,由此可看出line指针变量它的地址是(fp+4)也就是(sp+8)
    83e0:       ebffffb7        bl      82c4 <_init+0x20> // printf(line);
    83e4:       e24bd004        sub     sp, fp, #4
    83e8:       e8bd4800        pop     {fp, lr}
    83ec:       e28dd010        add     sp, sp, #16
    83f0:       e12fff1e        bx      lr

从main函数跳过来时,栈里存放的内容顺序:
  ['K'    ]
  [7788   ]

myprintf函数的前两句压栈后,栈里存放的内容顺序:
           ['K'             ]
           [7788            ] 
           ["nono"字符串的地址]  // 由r3寄存器存放压栈
           [22              ]  //  r2寄存器
           [11              ]  //  r1寄存器              
&line-->       ["hello."字符串地址]  // r0寄存器
           [返回地址         ]   // lr寄存器
sp-->          [fp寄存器原内容    ]  //最后栈顶在此内存单元位置

//注意,全部参数的地址都是连续的,只要获取其中一个参数的地址,即可通过偏移获取其它所有参数的值
//myprintf函数里的指针变量的地址是在sp+8字节的位置, 即&line + 4字节即是参数11的地址.

///

测试参数取值的代码:

test.c


#include <stdio.h>

void myprintf(char *line, ...)
{
    unsigned long *p = (unsigned long *)(&line);

    printf("%s\n", *p++); 
    printf("%d\n", *p++); 
    printf("%d\n", *p++); 
    printf("%s\n", *p++); 
    printf("%d\n", *p++); 
    printf("%c\n", *p++); 
}

int main(void)
{
    myprintf("hello test %d, %d, %s, %d, %c\n", 11, 22, "nono", 7788, 'K');

    return 0;
}


程序执行后的输出结果:
^_^ /mnt # ./a.out 
hello test %d, %d, %s, %d, %c

11
22
nono
7788
K
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值