1. 程式人生 > >05 printf函式可變引數的實現原理之彙編分析

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

相關推薦

05 printf函式可變引數實現原理彙編分析

如實現一個像printf函式格式的函式: test.c void myprintf(char *line, ...) // line指標變數是區域性變數,在棧裡分配空間 { printf(line); //呼叫printf時,r0存放字串地

06 溢位攻擊原理彙編分析

如c程式的程式碼: test.c: 1 2 #include <stdio.h> 3 4 int main(void) 5 { 6 int buf[10]

實現自己的printf列印 -- 可變引數函式

/*X86平臺,引數傳遞是基於堆疊來完成的,對記憶體使用時連續的*/ void printf_myself(const char *format, ...) { //char *ptr_s = &format; int num; char

C列印函式printf的一種實現原理簡要分析

【0】README 【1】printf函式程式碼分析: P1)line66: va_list arg = (va_list)((char*)(&fmt) + 4); 要知道,對

通過可變引數實現函式,求函式的平均值

#include<stdio.h>#include<stdarg.h>#include<Windows.h>#pragma warning (disable :4996)int getaver(int num,...){va_list ar

dubbo實現原理SPI簡介

ring 循環 -i OS ade ava rabl spi for循環   dubbo采用微內核+插件體系,設計優雅,擴展性很強。微內核+插件體系是如何實現的呢?想必大家都知道SPI(service provider interface)機制。這種機制的原理是假如我們定義

C++函式模板及實現原理

    C++為我們提供了函式模板機制。所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。     凡是函式體相同的函式都可以用這個模板來代替,不必定義多個函式,只需在模板中定義

stdarg.h 的使用 函式可變引數

全稱:standard arguments 成員: va_list :用來定義va變數,如va_list va。 va_start():使用方法:va_start(va,n) 其中va為va_list 定義的變數,n為引數個數。 va_arg() :使用方法:va_arg(va,typ

spring-AOP(二)實現原理AspectJ註解方式

在上一篇spring-AOP(一)實現原理我們瞭解瞭如何使用ProxyFactory來建立AOP代理物件,但其過程需要實現一些介面,並且需要一些比較複雜的配置。因此,在spring2.0之後,提供了一種較為便利的方式。 使用@Aspect註解宣告一個切面類,之後通過@EnableAspectJAutoProx

JDK1.5特性——函式可變引數

我們在寫函式的時候,要往函式裡面傳遞引數進行運算。          public static int add(int a,int b){         return a+b;     }          public static int add(int a,int

Windows下DEVC++ 5.11 的printf函式引數的執行順序

printf函式為其引數建立一個[棧],從右到左將引數壓入棧,再從棧內將裡面的元素依次列印。 函式舉例 #include <stdio.h> int p(int a) { print

java函式可變引數(不確定引數)的使用

java可變引數 當寫了一方方法後,想讓這個方法傳入不確定的引數值,就要用到可變引數 在jdk1.5加入了此方法,使用語法: 資料型別 ... 可變引數名稱 如:int ... data public class Test { public static v

Java多執行緒AQS(AbstractQueuedSynchronizer )實現原理和原始碼分析(三)

章節概覽、 1、回顧 上一章節,我們分析了ReentrantLock的原始碼: 2、AQS 佇列同步器概述 本章節我們深入分析下AQS(AbstractQueuedSynchronizer)佇列同步器原始碼,AQS是用來構建鎖或者其他同步元件的基礎框架。

Java多執行緒Condition實現原理和原始碼分析(四)

章節概覽、 1、概述 上面的幾個章節我們基於lock(),unlock()方法為入口,深入分析了獨佔鎖的獲取和釋放。這個章節我們在此基礎上,進一步分析AQS是如何實現await,signal功能。其功能上和synchronize的wait,notify一樣。

c語言 函式可變引數列表

1、編寫函式求一系列值的平均數,引數數目不確定: #include <stdarg.h> double average(int n_values, ...) { va_list var_arg; int count; double sum=0; va_st

coco2d-x中成員函式回撥實現原理

//標頭檔案 #ifndef __COOCS2D_CALLBACK_H__ #define __COOCS2D_CALLBACK_H__ #include <iostream> #include <string> using namespace std;

c++函式過載機制實現原理

一、c++函式過載的定義: 在同一作用域類,一組函式的函式名相同,引數列表不同(引數個數不同/引數型別不同),返回值可同可不同 二、函式過載的作用: 過載函式通常用來在同一個作用域內 用同一個函式名 命名一組功能相似的函式,這樣做減少了函式名的數量,避

如何用 linux 實現命令列引數可變引數實現

僅用main函式的引數實現一個整數計算器 #include <stdio.h> #include <string.h> #include <stdlib.h>

C語言可變引數原理

轉自:http://blog.csdn.net/bigloomy/article/details/6588354 這個寫得比較簡單,明瞭,看了這個才真正理解了變長引數怎麼實現的。 在學習C語言的過程中我們可能很少會去寫變參函式,印象中大學老師好像也沒有提及過,但我發現變參

Linux下函式可變引數va_arg_##__VA_ARGS__巨集

va_list //compile:gcc va_arg.c //run:./a.out //Notes:如果遇到獲取char,type用int,如果該用char會提示...傳遞時被提升為int。此處使用的編譯器是Linux下的gcc #include<stdio.