|
【方法】在Linux C程序中嵌入匯編語言 |
一派掌門 二十級 |
首先,編寫一個簡單的C程序: #include <stdio.h>
void fun(int *num, int *num2);
int main() { int a = 10, b = 32; fun(&a, &b); printf("a=%d, b=%d\n", a, b); return 0; } 其中主函數調用了fun函數。 fun函數我們打算用匯編語言來寫,其功能是把*num的值加15, *num2的值加5。
|
一派掌門 二十級 |
GLOBAL fun
fun: MOV ECX, [ESP+4] ; 把第一個參數的值送入ECX寄存器,其中存儲的是變量a的地址 ADD DWORD[ECX], 15 ; []表示取內存內容,DWORD就是類型int的大小,把ECX指向的內存區域的值加15 MOV ECX, [ESP+8] ; 取第二個參數的值 ADD DWORD[ECX], 5 RET ; 返回
|
|
一派掌門 二十級 |
Makefile文件: # 連接 test: test.o hello.o gcc -m32 test.o hello.o -o test
# 編譯test.c,生成32位的目標文件 test.o: test.c gcc -m32 -c test.c
# 編譯hello.asm,生成目標文件 hello.o: hello.asm nasm -f elf hello.asm
|
|
一派掌門 二十級 |
編譯,運行程序: octopus@pc3:~/Documents/Codes/C/link2$ make gcc -m32 -c test.c nasm -f elf hello.asm gcc -m32 test.o hello.o -o test octopus@pc3:~/Documents/Codes/C/link2$ ./test a=25, b=37 octopus@pc3:~/Documents/Codes/C/link2$
|
|
一派掌門 二十級 |
為了能在64位系統下編譯32位的程序,應該為gcc安裝: sudo apt-get install libc6-dev-i386
|
|
一派掌門 二十級 |
如果想要fun函數返回值,可以將其放入EAX寄存器裏面: MOV EAX, 12345678 ; 返回值 RET ; 返回
C程序部分: #include <stdio.h> int fun(int *num, int *num2); int main() { int a = 10, b = 32; int c = fun(&a, &b); printf("a=%d, b=%d\n", a, b); printf("c=%d\n", c); return 0; } 運行結果: $ ./test a=25, b=37 c=12345678
|
|
一派掌門 二十級 |
返回64位值的方法: GLOBAL fun fun: MOV EDX, 0xabcdef01 ; 返回值的高32位 MOV EAX, 0x12345678 ; 返回值的低32位 RET
#include <stdio.h> long long fun(); int main() { long long v = fun(); printf("返回值 = %lld\n", v); printf("高32位: %#08lx\n", (long)(v >> 32)); printf("低32位: %#08lx\n", (long)(v & 0xffffffff)); return 0; }
運行結果: 返回值 = -6066930335118764424 高32位: 0xabcdef01 低32位: 0x12345678
|
|
一派掌門 二十級 |
返回字符串指針的方法: GLOBAL fun fun: MOV EAX, msg ; 把數據段的地址作為返回值返回 RET
; 數據段 msg: DB "Hello, World!" DB 0
#include <stdio.h> char *fun(); int main() { char *str = fun(); puts(str); return 0; }
程序輸出: Hello, World!
|
|
一派掌門 二十級 |
其實,在C語言中可以直接通過引用外部變量的方法引用數據段msg: 【匯編部分】 GLOBAL msg msg: DB "Hello, World!" DB 0 【C語言部分】 #include <stdio.h> extern char msg; int main() { // 變量msg只是第一個字符 printf("第1個字符: %c\n", msg); // 要想輸出其他字符,必須先得到msg的地址,然後對這個地址進行加法運算 printf("第2個字符: %c\n", *(&msg + 1)); printf("第3個字符: %c\n", *(&msg + 2)); // 可以用下面的方法輸出整個字符串 char *pMsg = &msg; puts(pMsg); return 0; }
【輸出】 $ ./test 第1個字符: H 第2個字符: e 第3個字符: l Hello, World!
|
|
一派掌門 二十級 |
如果把msg變量聲明為數組的話,也能直接輸出字符串: #include <stdio.h> #include <string.h> extern char msg[]; int main() { puts(msg); printf("length: %d\n", strlen(msg)); return 0; } 運行結果: Hello, World! length: 13
但是,不能聲明成字符串指針: extern char *msg; 否則會出現段錯誤。
|
|
一派掌門 二十級 |
匯編語言中的除法指令: 【匯編部分】 GLOBAL div div: MOV AX, [ESP+4] DIV BYTE[ESP+8] RET 【C語言部分】 #include <stdio.h> short div(unsigned char a, unsigned char b); int main() { short num = div(10, 3); printf("%d ... %d\n", num & 0xff, num >> 8); return 0; } 運行結果: 3 ... 1
|
|
一派掌門 二十級 |
在匯編語言中調用C語言函數: 【匯編部分】 GLOBAL fun fun: CALL [ESP+4] RET 【C語言部分】 #include <stdio.h> void fun(void (*f)()); void test() { printf("This is a string.\n"); } int main() { fun(test); return 0; } 輸出: This is a string.
|
|
一派掌門 二十級 |
匯編語言創建C語言可寫的數組的方法: 【匯編部分】 SECTION .bss GLOBAL arr arr: RESB 20 【C語言部分】 #include <stdio.h> #include <string.h> extern char arr[20]; int main() { strcpy(arr, "This is a string."); puts(arr); return 0; }
輸出: This is a string.
|
|
一派掌門 二十級 |
關於匯編語言中的三個段: 【匯編部分】 ; BSS段中專門存放未初始化的變量 SECTION .bss GLOBAL arr arr: RESB 20
; 這個段中不可以定義函數 ;fun3: ; RET
; DATA段中專門存放已初始化的變量 ; 其中的內容可讀可寫 SECTION .data GLOBAL msg msg: DB "Hello, World!" DB 0
GLOBAL fun2 fun2: RET
; TEXT段中的內容是只讀的 SECTION .text GLOBAL str GLOBAL fun str: DB "abcdef" DB 0 fun: MOV EAX, 14 RET 【C語言部分】 #include <stdio.h> #include <string.h> extern char arr[20]; extern char msg[]; extern char str[]; int fun(); void fun2(); int main() { strcpy(arr, "This is a string."); puts(arr); msg[0] = 'I'; puts(msg); printf("%d\n", fun()); //str[0] = 'm'; // 只讀!將會引發段錯誤 puts(str); fun2(); return 0; } 【輸出】 This is a string. Iello, World! 14 abcdef
|
|
一派掌門 二十級 |
接下來我們來一點刺激的! 把一個C語言數組拿來執行!
【C語言部分】 #include <stdio.h> #include <string.h>
extern char fun, end; // fun函數的開始和結束位置 char codes[200];
int main() { int size = &end - &fun; // fun函數的機器代碼大小 printf("size=%d\n", size); memcpy(codes, &fun, size); // 把fun函數的機器代碼複製到codes數組中 // 執行codes數組中所存放的機器代碼,並讀取代碼執行完畢後EAX寄存器中的值 int (*fun)() = (int (*)())codes; int v = fun(); printf("%d\n", v); return 0; } 【匯編部分】 GLOBAL fun GLOBAL end fun: MOV EAX, 48 RET end:
【運行結果】 size=6 48
|
|
一派掌門 二十級 |
int (*fun)() = (int (*)())codes; 這句話的意思就是:先定義一個fun變量,變量的類型為函數指針。 然後把codes變量(本來是一個數組)強行轉換成函數指針,賦給fun變量。
|
|
一派掌門 二十級 |
16樓所示的代碼有點沒寫好,因為main函數中有兩個fun,所以乾脆改一個名字吧,更容易理解: #include <stdio.h> #include <string.h>
extern char fun, end; // fun函數的開始和結束位置 char codes[200];
int main() { int (*fff)(); // 定義一個函數指針 int v; int size = &end - &fun; // fun函數的機器代碼大小 printf("size=%d\n", size); memcpy(codes, &fun, size); // 把fun函數的機器代碼複製到codes數組中 // 執行codes數組中所存放的機器代碼,並讀取代碼執行完畢後EAX寄存器中的值 fff = (int (*)())codes; v = fff(); printf("%d\n", v); return 0; }
|
|
一派掌門 二十級 |
實際上,不需要定義函數指針,就能直接執行codes數組: v = ((int (*)())codes)(); printf("%d\n", v); 只不過括號比較多而已。
|
|