一:背景
1. 簡介
.NET 高階除錯要想玩的好,看懂彙編是基本功,但看懂彙編和能寫點彙編又完全是兩回事,所以有時候看的多,總手癢癢想寫一點,在 Windows 平臺上搭建彙編環境不是那麼容易,大多還是用微軟的 MASM + DosBox
搭一個 8086 的環境,這玩意距今快 50 年了。
在以前想快捷的寫一點彙編,藉助的是 VC 編譯器的 __asm
在 C/C++ 程式碼中內嵌一點,比如下面這樣。
int main()
{
int num = 10;
__asm {
mov[num], 20
}
printf("num=%d", num);
}
便捷是便捷,但只能玩個區域性,還是不夠爽,所以本篇我們藉助 nasm
來搭建一個 32bit 的彙編環境,當然 64bit 也是可以的, nasm
在 Linux 社群中非常有名。
二:搭建 x86 彙編環境
1. 前置基礎構件
- nasm 下載
nasm 是一個非常有名的彙編器,官方網址:https://nasm.us/
目前穩定版是 2.15.05
。
- gcc
大家都知道,原始碼要變成可執行程式,步驟一般是: asm -> obj -> exe
,前半部分由 nasm 負責,後半部分由 gcc 負責, gcc 是 Linux 上的剛需產品,在 Windows 上可以用 MinGW
。
下載網址:https://sourceforge.net/projects/mingw/files/MinGW
下載完之後,將下圖中的 五項
全部勾選上進行安裝。
把 bin, include,lib
全部配到環境變數的 PATH 中,然後開啟控制檯鍵入 gcc -v
看一下有沒有配好。
PS C:\Users\Administrator\Desktop> gcc -v
Using built-in specs.
COLLECT_GCC=C:\MinGW\bin\gcc.exe
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-6.3.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --with-gmp=/mingw --with-mpfr --with-mpc=/mingw --with-isl=/mingw --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-6.3.0-1' --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --enable-libgomp --disable-libvtv --enable-nls
Thread model: win32
gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
PS C:\Users\Administrator\Desktop>
- vscode 外掛
這裡我準備用 vscode
來寫彙編程式碼,主要安裝兩個外掛。
- The Netwide Assembler (NASM)
這個 nasm 官方提供的 語法高亮 外掛。
- GDB Debug
gdb 已經內嵌到了 gcc 中,方便於 code 除錯。
2. vscode 自動化構建
玩過 vscode 的朋友應該知道,自動化構建需要自己寫 tasks.json
,這裡我簡單寫了一個。
{
"version": "2.0.0",
"tasks": [
{
"label": "win86",
"type": "shell",
"command": "nasm.exe -f win32 -g -F cv8 -l app.lst app.asm; gcc app.obj -o app.exe",
"problemMatcher": {
"pattern": {
"regexp": "error"
}
},
"group": "build",
"presentation": {
"focus": true,
"panel": "dedicated",
"reveal": "silent",
"clear": true
}
}
]
}
然後就是配置啟動 launch.json
,程式碼如下:
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "GDB32",
"program": "${workspaceFolder}/app.exe",
"stopOnEntry": true,
"preLaunchTask": "win86"
},
{
"type": "gdb",
"request": "launch",
"name": "GDB64",
"program": "${workspaceFolder}/app.exe",
"stopOnEntry": true,
"preLaunchTask": "win64"
}
]
}
到這裡基礎設施就全部搭建完成了,然後就是寫一個簡單的彙編程式,實現三個 printf
的列印,程式碼如下:
extern _printf
SECTION .data
msg db 'Hello World!', 0Ah , 0h;
num1 dd 100;
num1_int_fmt db 'num1=%d', 0Ah, 0h;
num2 dq 3.14;
num2_flt_fmt db 'num2=%lf', 0Ah, 0h;
SECTION .text
global _main
_main:
push ebp
mov ebp , esp
; printf("Hello World\n");
mov eax , msg
push eax
call _printf
add esp, 4
; printf("num1=%d",num1)
mov eax , [num1]
push eax
mov ebx , num1_int_fmt
push ebx ,
call _printf
add esp , 4
; printf("num2=%lf",num2)
movq xmm0 , [num2]
sub esp , 0x8
movsd [esp] , xmm0
mov ebx , num2_flt_fmt
push ebx
call _printf
add esp , 0xc
mov esp , ebp
pop ebp
ret
輸出結果如下:
從上面的程式碼看,我需要自己協調棧平衡,自己去管理暫存器和記憶體的使用,真的是太爽了。
二:總結
彙編看多了,總想自己動手試試,如果你也有這種想法,可以搭建一下玩玩,有一點遺憾的是,在 windows 中用 gdb 單步除錯彙編目前還沒搞定,在 linux 上很輕鬆,不過也不影響自己學習研究,畢竟可以用強大的 windbg 和 ollydbg 來實現單步除錯,對吧!