|  | 【方法】在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);
 只不過括號比較多而已。
 
 | 
|