C語言的動態(tài)調(diào)用

我們都認(rèn)為C語言是一種非常靜態(tài)的語言坑匠,幾乎沒有什么動態(tài)特性后室,同時往往在編譯器就決定了整個運(yùn)行方式,運(yùn)行期是很難改變其運(yùn)行狀態(tài)的惊来。其實(shí)C語言也是可以比較動態(tài)的,只是由于C語言是一個跨平臺兼容語言棺滞,每個平臺都有不同的實(shí)現(xiàn)裁蚁,其動態(tài)化很難統(tǒng)一。這里我們看看在AArch64平臺上的動態(tài)化實(shí)現(xiàn)继准。

其他語言的動態(tài)化

腳本語言是非常具有動態(tài)特性的枉证,其中典型的js就可以如下方式動態(tài)調(diào)用方法。

function hello() {
  console.log('hello world')
}
eval('hello()')

平時開發(fā)常用的Objc也有一定的動態(tài)特性移必,比如NSInvocation

- (id)performSelector:(SEL)aSelector;

都可以通過方法名稱來調(diào)用刽严。

那么我們來看看C語言的表現(xiàn)。

C語言的動態(tài)化

根據(jù)上兩篇內(nèi)容避凝,我們了解了iOS/Mac系統(tǒng)的執(zhí)行文件格式MachO舞萄,而linux常用的ELF也是類似,執(zhí)行代碼都在TEXT段管削,如果我們要執(zhí)行對應(yīng)的方法倒脓,我們只需要拿到對應(yīng)的地址(也就是函數(shù)指針)就行了。

那么如何從字符串找到對應(yīng)的地址呢含思?這就涉及到函數(shù)符號表了崎弃,根據(jù)上篇的內(nèi)容,不難找到其對應(yīng)的函數(shù)指針含潘,這里系統(tǒng)也給我們提供了一個封裝好的方法饲做。

NAME
     dlsym -- get address of a symbol

SYNOPSIS
     #include <dlfcn.h>

     void*
     dlsym(void* handle, const char* symbol);

DESCRIPTION
     dlsym() returns the address of the code or data location specified by the null-terminated character string symbol.  Which libraries and bundles are searched depends on the handle parameter.

有了函數(shù)指針之后,只需要將我們的參數(shù)填入對應(yīng)位置遏弱,我們就可以實(shí)現(xiàn)方法調(diào)用了盆均。如何填入?yún)?shù)呢?根據(jù)之前的討論和aapcs64ARM官方文檔的說明漱逸,我們可以按照這種思路去填入?yún)?shù)泪姨。

這里我們簡單的把所有參數(shù)都認(rèn)為是int64(或者說void *)類型游沿,這樣我們可以把以上邏輯簡化為:

  1. 按x0-x7順序填入寄存器
  2. 剩下的都放入棧中

這里設(shè)計個簡單的動態(tài)調(diào)用接口:

extern void dynamic_call_func_name(const char *func, int64_t argc, int64_t *args) {
    void *funcPtr = dlsym(RTLD_DEFAULT, func);
    dynamic_call_func((uintptr_t)funcPtr, argc, args);
}
extern void dynamic_call_func(uintptr_t func, int64_t argc, int64_t *args);

以及測試函數(shù):

void one_arg(int64_t a1);
void two_arg(int64_t a1, int64_t a2);
void eight_arg(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t a8);
void nine_arg(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t a8, int64_t a9);
void ten_arg(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10);
void eleven_arg(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10, int64_t a11);
void more_arg(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, int64_t a8,
              int64_t aa1, int64_t aa2, int64_t aa3, int64_t aa4, int64_t aa5, int64_t aa6, int64_t aa7, int64_t aa8);

那么動態(tài)調(diào)用可以寫作:

int64_t args[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};
dynamic_call_func_name("eleven_arg", sizeof(args)/sizeof(int64_t), args);

這樣我們就實(shí)現(xiàn)了C語言的動態(tài)調(diào)用了。比如可以從其他端獲得方法名和參數(shù)列表肮砾,就可以直接調(diào)用C方法了诀黍。這也是一些高級語言調(diào)用C語言的實(shí)現(xiàn)方式(cpython),以及一些rpc的實(shí)現(xiàn)方案仗处。

接下來我們來看看如何填入?yún)?shù)眯勾,dynamic_call_func的實(shí)現(xiàn)方式。

動態(tài)設(shè)置參數(shù)

這里我們只能通過匯編來設(shè)置參數(shù)了婆誓。

.align 4

// C方法會自動加上前綴`_`
.global _dynamic_call_func

_dynamic_call_func:
// if func == NULL then return
cbz x0, DCReturnZero

// 申請棾曰罚空間,0x10用于緩存fp和lr旷档,剩下的用于臨時變量
// 這里不能確定調(diào)用的方法是否會用到椖P穑空間來傳遞參數(shù)歇拆,所以這里暫不考慮鞋屈,fp == sp
sub sp, sp, #0x20
stp x29, x30, [sp]
mov x29, sp

// 緩存一些入?yún)ⅲ枰o下個方法騰出寄存器
// x9 = func
// x10 = x11 = argc
// x12 = x13 = args
mov x9, x0
mov x10, x1
mov x11, x1
mov x12, x2
mov x13, x2

// 沒有參數(shù)直接 CALL
cbz x11, DCCallFunc

// 第一個參數(shù)
ldr x0, [x12]
sub x11, x11, #1
cbz x11, DCCallFunc

// 第二個參數(shù)
ldr x1, [x12, #0x8]
sub x11, x11, #1
cbz x11, DCCallFunc

ldr x2, [x12, #0x10]
sub x11, x11, #1
cbz x11, DCCallFunc

ldr x3, [x12, #0x18]
sub x11, x11, #1
cbz x11, DCCallFunc

ldr x4, [x12, #0x20]
sub x11, x11, #1
cbz x11, DCCallFunc

ldr x5, [x12, #0x28]
sub x11, x11, #1
cbz x11, DCCallFunc

ldr x6, [x12, #0x30]
sub x11, x11, #1
cbz x11, DCCallFunc

// 第八個參數(shù)
ldr x7, [x12, #0x38]
sub x11, x11, #1
cbz x11, DCCallFunc

// 棧參數(shù)
// 開始計算椆拭伲空間厂庇,由于我們的參數(shù)都是int64類型
// 所以棧空間x15 = x11(剩余參數(shù)個數(shù)) * 8
mov x16, #8
mul x15, x11, x16
// The NSAA is rounded up to the larger of 8 or the Natural Alignment of the argument’s type
// 這里需要對齊输吏,我也不明白為什么
and x16, x15, #0x8
cbz x16, DCNoFixAlign
DCFixAlign:
add x15, x15, #0x8
DCNoFixAlign:

// 現(xiàn)在開始重新申請參數(shù)椚酰空間,并將椆峤Γ空間大小存入臨時變量`fp + 0x18`
DCStoreStackArgsLength:
str x15, [x29, #0x18]
sub sp, sp, x15

mov x15, sp
add x13, x12, #0x38

// 循環(huán)剩下的參數(shù)拄氯,逐個將其入棧:
// for arg in args+8:
//   push(arg)
DCPushStackArgs:
add x13, x13, #0x8
ldr x14, [x13]
str x14, [x15]
sub x11, x11, #1
add x15, x15, #0x8
cbnz x11, DCPushStackArgs


// CALL
DCCallFunc:
blr x9

// 這里首先銷毀參數(shù)棧空間
ldr x15, [x29, #0x18]
cbz x15, DCRestoreStack
DCRestoreStackArgsLength:
add sp, sp, x15

// 然后還原fp, lr
// 銷毀當(dāng)前椝常空間
DCRestoreStack:
ldp x29, x30, [sp]
add sp, sp, #0x20

ret

// ReturnZero:
DCReturnZero:
mov x0, 0
ret

經(jīng)過測試译柏,可以看到所有參數(shù)都被正確的傳遞過去了,說明這種思路是正確的姐霍。

總結(jié)

那么C語言動態(tài)調(diào)用能給我們一些什么好處呢鄙麦。這是一種rpc的思想,而且這不需要額外的rpc支持镊折,就可以直接調(diào)用幾乎所有C方法胯府,但是這樣也給我們的程序帶來了一定的風(fēng)險,包括權(quán)限恨胚,參數(shù)類型等問題骂因。

同時也是快速實(shí)現(xiàn),或者說兼容C實(shí)現(xiàn)高級語言的一種方式赃泡,比如cpython就是利用了這種思想侣签。

由于不同平臺的差異性塘装,可能會導(dǎo)致兼容工作非常龐大,那么我們可以設(shè)計幾種類型的參數(shù)影所,或者固定幾個參數(shù)蹦肴,來簡化我們的兼容工作,比如將所有的對象都放到堆上猴娩,使用指針來傳遞阴幌。

開源項(xiàng)目libffi實(shí)現(xiàn)了多平臺的動態(tài)調(diào)用,有興趣的人可以自己去了解其實(shí)現(xiàn)卷中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矛双,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蟆豫,更是在濱河造成了極大的恐慌议忽,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件十减,死亡現(xiàn)場離奇詭異栈幸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)帮辟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門速址,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人由驹,你說我怎么就攤上這事芍锚。” “怎么了蔓榄?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵并炮,是天一觀的道長。 經(jīng)常有香客問我甥郑,道長逃魄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任壹若,我火速辦了婚禮嗅钻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘店展。我一直安慰自己养篓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布赂蕴。 她就那樣靜靜地躺著柳弄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碧注,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天嚣伐,我揣著相機(jī)與錄音,去河邊找鬼萍丐。 笑死轩端,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逝变。 我是一名探鬼主播基茵,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壳影!你這毒婦竟也來了拱层?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宴咧,失蹤者是張志新(化名)和其女友劉穎根灯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掺栅,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烙肺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柿冲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茬高。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡兆旬,死狀恐怖假抄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情丽猬,我是刑警寧澤宿饱,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站脚祟,受9級特大地震影響谬以,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜由桌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一为黎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧行您,春花似錦铭乾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌斧,卻和暖如春笛质,著一層夾襖步出監(jiān)牢的瞬間泉沾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工妇押, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跷究,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓敲霍,卻偏偏與公主長得像揭朝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子色冀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志閱讀 24,813評論 2 38
  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型潭袱。 運(yùn)用指針編程是C語言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu)锋恬; ...
    朱森閱讀 3,446評論 3 44
  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用屯换。當(dāng)一個函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,616評論 1 19
  • 如期而至的Repository篇彤悔,內(nèi)部實(shí)現(xiàn)則由Realm、Retrofit索守,以及內(nèi)存級LruCache組成晕窑。Rep...
    MarkZhai閱讀 2,136評論 3 25
  • 曾經(jīng)這樣總結(jié)我自己:我一年里有九個月浪蕩著度過的,像一匹野馬卵佛。但也會有三個月是心定了的杨赤,像一只溫順的貓。如果你在那...
    馬哇賽閱讀 501評論 0 0