<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 第二章 Hello,world! 讓我們用最著名的代碼例子開始吧: ``` #!cpp #include <stdio.h> int main() { printf("hello, world"); return 0; }; ``` ## 2.1 x86 ### 2.1.1 MSVC-x86 在MSVC 2010中編譯一下: ``` #!bash cl 1.cpp /Fa1.asm ``` (/Fa 選項表示生產匯編列表文件) ``` #!bash CONST SEGMENT $SG3830 DB 'hello, world', 00H CONST ENDS PUBLIC _main EXTRN _printf:PROC ; Function compile flags: /Odtp _TEXT SEGMENT _main PROC push ebp mov ebp, esp push OFFSET $SG3830 call _printf add esp, 4 xor eax, eax pop ebp ret 0 _main ENDP _TEXT ENDS ``` MSVC生成的是Intel匯編語法。Intel語法與AT&T語法的區別將在后面討論。 編譯器會把1.obj文件連接成1.exe。 在我們的例子當中,文件包含兩個部分:CONST(放數據)和_TEXT(放代碼)。 字符串“hello,world”在 C/C++ 類型為`const char*`,然而他沒有自己的名稱。 編譯器需要處理這個字符串,就自己給他定義了一個$SG3830。 所以例子可以改寫為: ``` #!cpp #include <stdio.h> const char *$SG3830="hello, world"; int main() { printf($SG3830); return 0; }; ``` 我們回到匯編列表,正如我們看到的,字符串是由0字節結束的,這也是 C/C++的標準。 在代碼部分,`_TEXT`,只有一個函數:main()。 函數main()與大多數函數一樣都有開始的代碼與結束的代碼。 函數當中的開始代碼結束以后,調用了printf()函數:`CALL _printf`。 在PUSH指令的幫助下,我們問候語字符串的地址(或指向它的指針)在被調用之前存放在棧當中。 當printf()函數執行完返回到main()函數的時候,字符串地址(或指向它的指針)仍然在堆棧中。 當我們都不再需要它的時候,堆棧指針(ESP寄存器)需要改變。 ``` #!bash ADD ESP, 4 ``` 意思是ESP寄存器加4。 為什么是4呢?由于是32位的代碼,通過棧傳送地址剛好需要4個字節。 在64位系統當中它是8字節。 “`ADD ESP, 4`” 實際上等同于“`POP register`”。 一些編輯器(如Intel C++編譯器)在同樣的情況下可能會用 POP ECX代替ADD(例如這樣的模式可以在Oracle RDBMS代碼中看到,因為它是由Intel C++編譯器編譯的),這條指令的效果基本相同,但是ECX的寄存器內容會被改寫。 Intel C++編譯器可能用`POP ECX`,因為這比`ADD ESP, X`需要的字節數更短,(1字節對應3字節)。 在調用printf()之后,在C/C++代碼之后執行`return 0`,return 0是main()函數的返回結果。 代碼被編譯成指令 `XOR EAX, EAX` XOR事實上就是異或,但是編譯器經常用它來代替 `MOV EAX, 0` 原因就是它需要的字節更短(2字節對應5字節)。 有些編譯器用`SUB EAX, EAX` 就是EXA的值減去EAX,也就是返回0。 最后的指令RET 返回給調用者,他是C/C++代碼吧控制返還給操作系統。 ### 2.1.2 GCC-x86 現在我們嘗試同樣的C/C++代碼在linux中的GCC 4.4.1編譯 ``` #!bash gcc 1.c -o 1 ``` 下一步,在IDA反匯編的幫助下,我們看看main()函數是如何被創建的。 (IDA,與MSVC一樣,也是顯示Intel語法)。 我也可以是GCC生成Intel語法的匯編代碼,添加參數 ``` #!bash -S -masm=intel ``` 匯編代碼: ``` #!bash main proc near var_10 = dword ptr -10h push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 10h mov eax, offset aHelloWorld ; "hello, world" mov [esp+10h+var_10], eax call _printf mov eax, 0 leave retn main endp ``` 結果幾乎是相同的,“hello,world”字符串地址(保存在data段的)一開始保存在EAX寄存器當中,然后保存到棧當中。 同樣的在函數開始我們看到了 ``` AND ESP, 0FFFFFFF0h ``` 這條指令該指令對齊在16字節邊界在ESP寄存器中的值。這導致堆棧對準的所有值。 `SUB ESP,10H`在棧上分配16個字節。 這里其實只需要4個字節。 這是因為,分配堆棧的大小也被排列在一個16字節的邊界。 該字符串的地址(或這個字符串指針),不使用PUSH??指令,直接寫入到堆棧空間。 var_10,是一個局部變量,也是printf()的參數。 ?然后調用printf()函數。 不像MSVC,當gcc編譯不開啟優化,它使用MOV EAX,0清空EAX,而不是更短的代碼。 最后一條指令,LEAVE相當于MOV ESP,EBP和POP EBP兩條指令。 換句話說,這相當于指令將堆棧指針(ESP)恢復,EBP寄存器到其初始狀態。 這是必須的,因為我們在函數的開頭修改了這些寄存器的值(ESP和EBP)(執行MOV EBP,ESP/AND ESP...)。 ### 2.1.3 GCC:AT&T 語法 我們來看一看在AT&T當中的匯編語法,這個語法在UNIX當中更普遍。 ``` #!bash gcc -S 1_1.c ``` 我們將得到這個: ``` #!bash .file "1_1.c" .section .rodata .LC0: .string "hello, world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call printf movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" .section .note.GNU-stack,"",@progbits ``` 有很多的宏(用點開始)。現在為了簡單起見,我們先不看這些。(除了 .string ,就像一個C字符串編碼一個null結尾的字符序列)。然后,我們將看到這個: ``` #!bash .LC0: .string "hello, world" main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call printf movl $0, %eax leave ret ``` 在Intel與AT&T語法當中比較重要的區別就是: 操作數寫在后面 ``` 在Intel語法中:<instruction> <destination operand> <source operand> 在AT&T語法中:<instruction> <source operand> <destination operand> ``` 有一個理解它們的方法: 當你面對intel語法的時候,你可以想象把等號放到2個操作數中間,當面對AT&T語法的時候,你可以放一個右箭頭(→)到兩個操作數之間。 AT&T: 在寄存器名之前需要寫一個百分號(%)并且在數字前面需要美元符($)。方括號被圓括號替代。 AT&T: 一些用來表示數據形式的特殊的符號 ``` l long(32 bits) w word(16bits) b byte(8 bits) ``` 讓我們回到上面的編譯結果:它和在IDA里看到的是一樣的。只有一點不同:0FFFFFFF0h 被寫成了$-16,但這是一樣的,10進制的16在16進制里表示為0x10。-0x10就等同于0xFFFFFFF0(這是針對于32位構架)。 外加返回值這里用的MOV來設定為0,而不是用XOR。MOV僅僅是加載(load)了變量到寄存器。指令的名稱并不直觀。在其他的構架上,這條指令會被稱作例如”load”這樣的。 ## 2.2 x86-64 ### 2.2.1 MSVC-x86-64 讓我們來試試64-bit的MSVC: ``` #!bash $SG2989 DB ’hello, world’, 00H main PROC sub rsp, 40 lea rcx, OFFSET FLAT:$SG2923 call printf xor eax, eax add rsp, 40 ret 0 main ENDP ``` 在x86-64里,所有被擴展到64位的寄存器都有R-前綴。并且盡量不用棧來傳遞函數的參數了,大量使用寄存器來傳遞參數,非常類似于fastcall。 在win64里,RCX,RDX,R8,R9寄存器被用來傳遞函數參數,如果還有更多就使用棧,在這里我們可以看到printf()函數的參數沒用通過棧來傳遞,而是使用了rcx。 讓我們針對64位來看,作為64位寄存器會有R-前綴,并且這些寄存器向下兼容,32位的部分使用E-前綴。 如下圖所示,這是RAX/EAX/AX/AL在64位x86兼容cpu里的情況 ? ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2a65699.jpg) 在main()函數會返回一個int類型的值,在64位的程序里為了兼容和移植性,還是用32位的,所以可以看到EAX(寄存器的低32位部分)在函數最后替代RAX被清空成0。 ### 2.2.2 GCC-x86-64 這次試試GCC在64位的Linux里: ``` #!bash .string "hello, world" main: sub rsp, 8 mov edi, OFFSET FLAT:.LC0 ; "hello, world" xor eax, eax ; number of vector registers passed call printf xor eax, eax add rsp, 8 ret ``` 在Linux,*BSD和Mac OS X里使用同一種方式來傳遞函數參數。頭6個參數使用`RDI,RSI,RDX,RCX,R8,R9`來傳遞的,剩下的要靠棧。 所以在這個程序里,字串的指針被放到EDI(RDI的低32位部)。為什么不是64位寄存器RDI那? 這是一個重點,在64位模式下,對低32位進行操作的時候,會清空高32位的內容。比如 MOV EAX,011223344h將會把值寫到RAX里,并且清空RAX的高32位區域。 如果我們打開編譯好的對象文件(object file(.o)),我們會看到所有的指令: Listing 2.8:GCC 4.4.6 x64 ``` #!bash .text:00000000004004D0 main proc near .text:00000000004004D0 48 83 EC 08 sub rsp, 8 .text:00000000004004D4 BF E8 05 40 00 mov edi, offset format ; "hello, world" .text:00000000004004D9 31 C0 xor eax, eax .text:00000000004004DB E8 D8 FE FF FF call _printf .text:00000000004004E0 31 C0 xor eax, eax .text:00000000004004E2 48 83 C4 08 add rsp, 8 .text:00000000004004E6 C3 retn .text:00000000004004E6 main endp ``` 就像看到的那樣,在04004d4那行給edi寫字串指針的那句花了5個bytes。如果把這句換做給rdi寫指針,會花掉7個bytes.就是說GCC在試圖節省空間,為此數據段(data segment)中包含的字串不會被分配到高于4GB地址的空間上。 可以看到在printf()函數調用前eax被清空了,這樣做事因為要eax被用作傳遞向量寄存器(vector registers)的個數。 參考【21】 MichaelMatz/JanHubicka/AndreasJaeger/MarkMitchell. Systemvapplicationbinaryinterface.amdarchitecture processor supplement, . Also available as http://x86-64.org/documentation/abi.pdf. ## 2.3 ARM 根據作者自身對ARM處理器的經驗,選擇了2款在嵌入式開發流行的編譯器,Keil Release 6/2013和蘋果的Xcode 4.6.3 IDE(其中使用了LLVM-GCC4.2編譯器),這些可以為ARM兼容處理器和系統芯片(System on Chip)(SOC))來進行編碼。比如ipod/iphone/ipad,windows8 rt,并且包括raspberry pi。 ### 2.3.1 未進行代碼優化的Keil編譯:ARM模式 讓我們在Keil里編譯我們的例子 ``` #!bash armcc.exe –arm –c90 –O0 1.c ``` armcc編譯器可以生成intel語法的匯編程序列表,但是里面有高級的ARM處理器相關的宏,對我們來講更希望看到的是IDA反匯編之后的結果。 ``` Listing 2.9: Non-optimizing Keil + ARM mode + IDA #!bash .text:00000000 main .text:00000000 10 40 2D E9 STMFD SP!, {R4,LR} .text:00000004 1E 0E 8F E2 ADR R0, aHelloWorld ; "hello, world" .text:00000008 15 19 00 EB BL __2printf .text:0000000C 00 00 A0 E3 MOV R0, #0 .text:00000010 10 80 BD E8 LDMFD SP!, {R4,PC} .text:000001EC 68 65 6C 6C+aHelloWorld DCB "hello, world",0 ; DATA XREF: main+4 ``` 針對ARM處理器,我們需要預備一點知識,要知道ARM處理器至少有2種模式:ARM模式和thumb模式,在ARM模式下,所有的指令都被激活并且都是32位的。在thumb模式下所有的指令都是16位的。Thumb模式比較需要注意,因為程序可能需要更為緊湊,或者當微處理器用的是16位內存地址時會執行的更快。但也存在缺陷,在thumb模式下可用的指令沒ARM下多,只有8個寄存器可以訪問,有時候ARM模式下一條指令就能解決的問題,thumb模式下需要多個指令來完成。 從ARMv7開始引入了thumb-2指令集。這是一個加強的thumb模式。擁有了更多的指令,通常會有誤解,感覺thumb-2是ARM和thumb的混合。Thumb-2加強了處理器的特性,并且媲美ARM模式。程序可能會混合使用2種模式。其中大量的ipod/iphone/ipad程序會使用thumb-2是因為Xcode將其作為了默認模式。 在例子中,我們可以發現所有指令都是4bytes的,因為我們編譯的時候選擇了ARM模式,而不是thumb模式。 最開始的指令是”`STMFD SP!, {R4, LR}`”,這條指令類似x86平臺的PUSH指令,會寫2個寄存器(R4和LR)的變量到棧里。不過在armcc編譯器里輸出的匯編列表里會寫成”PUSH {R4, LR}”,但這并不準確,因為PUSH命令只在thumb模式下有,所以我建議大家注意用IDA來做反匯編工具。 這指令開始會減少SP的值,已加大棧空間,并且將R4和LR寫入分配好的棧里。 這條指令(類似于PUSH的STMFD)允許一次壓入好幾個值,非常實用。有一點跟x86上的PUSH不同的地方也很贊,就是這條指令不像x86的PUSH只能對sp操作,而是可以指定操作任意的寄存器。 “ADR R0, aHelloWorld”這條指令將PC寄存器的值與”hello, world”字串的地址偏移相加放入R0,為什么說要PC參與這個操作那?這是因為代碼是PIC(position-independet code)的,這段代碼可以獨立在內存運行,而不需要更改內存地址。ADR這條指令中,指令中字串地址和字串被放置的位置是不同的。但變化是相對的,這要看系統是如何安排字串放置的位置了。這也就說明了,為何每次獲取內存中字串的絕對地址,都要把這個指令里的地址加上PC寄存器里的值了。 ”`BL __2print`”這條指令用于調用printf()函數,這是來說下這條指令時如何工作的: ``` 將BL指令(0xC)后面的地址寫入LR寄存器; 然后把printf()函數的入口地址寫入PC寄存器,進入printf()函數。 ``` 當printf()函數完成之后,函數會通過LR寄存器保存的地址,來進行返回操作。 函數返回地址的存放位置也正是“純”RISC處理器(例如:ARM)和CISC處理器(例如x86)的區別。 另外,一個32位地址或者偏移不能被編碼到BL指令里,因為BL指令只有24bits來存放地址,所有的ARM模式下的指令都是4bytes(32bits),所以一條指令里不能放滿4bytes的地址,這也就意味著最后2bits總會被設置成0,總的來說也就是有26bits的偏移(包括了最后2個bit一直被設為0)會被編碼進去。這也夠去訪問大約±32M的了。 下面我們來看“MOV R0, #0“這條語句,這條語句就是把0寫到了R0寄存器里,這是因為C函數返回了0,返回值當然是放在R0里的。 最后一條指令是”LDMFD SP!, R4,PC“,這條指令的作用跟開始的那條STMFD正好相反,這條指令將棧上的值保存到R4和PC寄存器里,并且增加SP棧寄存器的值。這非常類似x86平臺里的POP指令。最前面那條STMFD指令成對保存了R4,和LR寄存器,LDMFD的時候將當時這兩個值保存到了R4和PC里完成了函數的返回。 我前面也說過,函數的返回地址會保存到LD寄存器里。在函數的最開始會把他保存到棧里,這是因為main()函數里還需要調用printf()函數,這個時候就會影響LD寄存器。在函數的最后就會將LD拿出棧放入PC寄存器里,完成函數的返回操作。最后C/C++程序的main()函數會返回到類似系統加載器上或者CRT里面。 匯編代碼里的DCB關鍵字用來定義ASCII字串數組,就像x86匯編里的DB關鍵字。 ### 2.3.2未進行代碼優化的Keil編譯: thumb模式 讓我們用下面的指令講例程用Keil的thumb模式來編譯一下。 ``` #!bash armcc.exe –thumb –c90 –O0 1.c ``` 我們可以在IDA里得到下面這樣的代碼: Listing 2.10:Non-optimizing Keil + thumb mode + IDA ``` #!bash .text:00000000 main .text:00000000 10 B5 PUSH {R4,LR} .text:00000002 C0 A0 ADR R0, aHelloWorld ; "hello, world" .text:00000004 06 F0 2E F9 BL __2printf .text:00000008 00 20 MOVS R0, #0 .text:0000000A 10 BD POP {R4,PC} .text:00000304 68 65 6C 6C+aHelloWorld DCB "hello, world",0 ; DATA XREF: main+2 ``` 我們首先就能注意到指令都是2bytes(16bits)的了,這正是thumb模式的特征,BL指令作為特例是2個16bits來構成的。只用16bits沒可能加載printf()函數的入口地址到PC寄存器。所以前面的16bits用來加載函數偏移的高10bits位,后面的16bits用來加載函數偏移的低11bits位,正如我說過的,所有的thumb模式下的指令都是2bytes(16bits)。但是這樣的話thumb指令就沒法使用更大的地址。就像上面那樣,最后一個bits的地址將會在編碼指令的時候省略。總的來講,BL在thumb模式下可以訪問自身地址大于±2M大的周邊的地址。 至于其他指令:PUSH和POP,它們跟上面講到的STMFD跟LDMFD很類似,但這里不需要指定SP寄存器,ADR指令也跟上面的工作方式相同。MOVS指令將函數的返回值0寫到了R0里,最后函數返回。 ### 2.3.3開啟代碼優化的Xcode(LLVM)編譯: ARM模式 Xcode 4.6.3不開啟代碼優化的情況下,會產生非常多冗余的代碼,所以我們學習一個盡量小的版本。 開啟-O3編譯選項 ``` #!bash Listing2.11:Optimizing Xcode(LLVM)+ARM mode __text:000028C4 _hello_world __text:000028C4 80 40 2D E9 STMFD SP!, {R7,LR} __text:000028C8 86 06 01 E3 MOV R0, #0x1686 __text:000028CC 0D 70 A0 E1 MOV R7, SP __text:000028D0 00 00 40 E3 MOVT R0, #0 __text:000028D4 00 00 8F E0 ADD R0, PC, R0 __text:000028D8 C3 05 00 EB BL _puts __text:000028DC 00 00 A0 E3 MOV R0, #0 __text:000028E0 80 80 BD E8 LDMFD SP!, {R7,PC} __cstring:00003F62 48 65 6C 6C+aHelloWorld_0 DCB "Hello world!",0 ``` STMFD和LDMFD對我們來說已經非常熟悉了。 MOV指令就是將0x1686寫入R0寄存器里。這個值也正是字串”Hello world!”的指針偏移。 R7寄存器里放入了棧地址,我們繼續。 MOVT R0, #0指令時將R0的高16bits寫入0。這是因為普通情況下MOV這條指令在ARM模式下,只對低16bits進行操作。需要記住的是所有在ARM模式下的指令都被限定在32bits內。當然這個限制并不影響2個寄存器直接的操作。這也是MOVT這種寫高16bits指令存在的意義。其實這樣寫的代碼會感覺有點多余,因為”`MOVS R0,#0x1686`”這條指令也能把高16位清0。或許這就是相對于人腦來說編譯器的不足。 “`ADD R0,PC,R0`“指令把R0寄存器的值與PC寄存器的值進行相加并且保存到R0寄存器里面,用來計算”Hello world!”這個字串的絕對地址。上面已經介紹過了,這是因為代碼是PIC(Position-independent code)的,所以這里需要這么做。 BL指令用來調用printf()的替代函數puts()函數。 GCC將printf()函數替換成了puts()。因為printf()函數只有一個參數的時候跟puts()函數是類似的。 printf()函數的字串參數里存在特殊控制符(例如 ”%s”,”\n” ,需要注意的是,程序里字串里沒有“\n”,因為在puts()函數里這是不需要的)的時候,兩個函數的功效就會不同。 為什么編譯器會替換printf()到puts()那?因為puts()函數更快。 puts()函數效率更快是因為它只是做了字串的標準輸出(stdout)并不用比較%符號。 下面,我們可以看到非常熟悉的”MOV R0, #0”指令,用來將R0寄存器設為0。 ### 2.3.4 開啟代碼優化的Xcode(LLVM)編譯thumb-2模式 在默認情況下,Xcode4.6.3會生成如下的thumb-2代碼 Listing 2.12:Optimizing Xcode(LLVM)+thumb-2 mode ``` #!bash __text:00002B6C _hello_world __text:00002B6C 80 B5 PUSH {R7,LR} __text:00002B6E 41 F2 D8 30 MOVW R0, #0x13D8 __text:00002B72 6F 46 MOV R7, SP __text:00002B74 C0 F2 00 00 MOVT.W R0, #0 __text:00002B78 78 44 ADD R0, PC __text:00002B7A 01 F0 38 EA BLX _puts __text:00002B7E 00 20 MOVS R0, #0 __text:00002B80 80 BD POP {R7,PC} ... __cstring:00003E70 48 65 6C 6C 6F 20+aHelloWorld DCB "Hello world!",0xA,0 ``` BL和BLX指令在thumb模式下情況需要我們回憶下剛才講過的,它是由一對16-bit的指令來構成的。在thumb-2模式下這條指令跟thumb一樣被編碼成了32-bit指令。非常容易觀察到的是,thumb-2的指令的機器碼也是從0xFx或者0xEx的。對于thumb和thumb-2模式來說,在IDA的結果里機器碼的位置和這里是交替交換的。對于ARM模式來說4個byte也是反向的,這是因為他們用了不同的字節序。所以我們可以知道,MOVW,MOVT.W和BLX這幾個指令的開始都是0xFx。 在thumb-2指令里有一條是”MOVW R0, #0x13D8”,它的作用是寫數據到R0的低16位里面。 “`MOVT.W R0, #0`”的作用類似與前面講到的MOVT指令,但它可以工作在thumb-2模式下。 還有些跟上面不同的地方,比如BLX指令替代了上面用到的BL指令,這條指令不僅將控制puts()函數返回的地址放入了LR寄存器里,并且講代碼從thumb模式轉換到了ARM模式(或者ARM轉換到thumb(根據現有情況判斷))。這條指令跳轉到下面這樣的位置(下面的代碼是ARM編碼模式)。 ``` #!bash __symbolstub1:00003FEC _puts ; CODE XREF: _hello_world+E __symbolstub1:00003FEC 44 F0 9F E5 LDR PC, =__imp__puts ``` 可能會有細心的讀者要問了:為什么不直接跳轉到puts()函數里面去? 因為那樣做會浪費內存空間。 很多程序都會使用額外的動態庫(dynamic libraries)(Windows里面的DLL,還有*NIX里面的.so,MAC OS X里面的.dylib),通常使用的庫函數會被放入動態庫中,當然也包括標準C函數puts()。 在可執行的二進制文件里(Windows的PE里的.exe文件,ELF和Mach-O文件)都會有輸入表段。它是一個用來引入額外模塊里模塊名稱和符號(函數或者全局變量)的列表。 系統加載器(OS loader)會加載所有需要的模塊,當在主模塊里枚舉輸入符號的時候,會把每個符號正確的地址與相應的符號確立起來。 在我們的這個例子里,`__imp__puts`就是一個系統加載器加載額外模塊的32位的地址值。LDR指令只需要把這個值加載到PC里面去,就可以控制程序流程到puts()函數里去。 所以只需要在系統加載器里的時候,一次性的就能將每個符號所對應的地址確定下來,這是個提高效率的好方式。 外加,我們前面也指出過,我們沒辦法只用一條指令并且不做內存操作的情況下就將一個32bit的值保存到寄存器里,ARM并不是唯一的模式的情況下,程序里去跳入動態庫中的某個函數里,最好的辦法就是這樣做一些類似與上面這樣單一指令的函數(稱做thunk function),然后從thumb模式里也能去調用。 在上面的例子(ARM編譯的那個例子)中BL指令也是跳轉到了同一個thunk function里。盡管沒有進行模式的轉變(所以指令里不存在那個”X”)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看