罗云彬老师的<Windows环境下32位汇编语言程序设计>
书中十三章再讲Console下exe程序输入参数的问题引入了两个过程_argc和_argv,书中并没有给出详细解释,在这里总结了下。
这两个过程产生的原因是应为汇编不像C语言调用SDK在EP函数就自动帮助传入参数,大家都知道:
int APIENTRY main(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, //这个就是程序的命令行参数
int nCmdShow)
又或者在C语言CUI编程中:
int main(int argc, char **argv) {
return 0;
}
其中argc就是参数的总数,而argv指向的就是包含每一个参数的指针数组
比如: C:\> nc 192.168.0.155 5750
那么argc就是3,argv分别是nc, 192.168.0.155, 5750
但是在汇编中这个过程不会帮我们做好。需要自己来做。可以自己写一个简单的程序调用GetCommandLine来获取输入的参数:
.386
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data?
szBuffer db 1024 dup(?)
.data
szTitle db \'GetCommandLine Results\', 0
.code
start:
invoke RtlZeroMemory, addr szBuffer, sizeof szBuffer
invoke GetCommandLine
mov esi, eax
invoke MessageBox, NULL, esi, addr szTitle, MB_OK
invoke ExitProcess, NULL
end start
比如我在控制台输入:
由此可见GetCommandLine函数只帮我们做了传入全部参数的功能而非像argc计入参数总数,argv计入每个参数是什么
而_argc和_argv两个过程就是帮我们做这两个事情的
贴代码:
CHAR_BLANK EQU 20h ;这是空格的Ascii的十六进制形式
CHAR_DELI EQU \'"\'
;----------------------------------------
_argc proc
local @dwArgc
; * @dwArgc这个临时变量用于记录输入参数总数
;----------------------------------------
pushad ;通用寄存器入栈
mov @dwArgc, 0 ;首先置0
invoke GetCommandLine ;获取命令行,地址在eax中
mov esi, eax
cld ;清除方向寄存器
_argc_loop:
lodsb ;把命令行字符串的第一个字符载入al中
or al, al ;判断字符串以及到了最后一个字符即0
jz _argc_end ;如果ZF置位即已经到达最后一个字符
cmp al, CHAR_BLANK ;由于刚开始的时候可能有空格先判断空格
jz _argc_loop ;如果是空格那么继续往下载入字符直到碰到不是空格为止
;-----------------------------------------------
; 到这里代表已经碰到了第一个非空格字符
; 由于lodsb类似C语言中的i++,先传值后挪地址
; 所以esi要减1回到前面判断为非空格的字符那里
;-----------------------------------------------
dec esi
inc @dwArgc ;是第一个参数了就是那个a.exe
_argc_loop1: ;这个循环是处理参数的,直到碰到空格才停
lodsb
or al, al ;首先判断该参数是不是最后一个参数即跟了0字符
jz _argc_end
cmp al, CHAR_BLANK
jz _argc_loop ;碰到空格就停
cmp al, CHAR_DELI
jnz _argc_loop1 ;如果碰到了"号
;---------------------------------------------------
; 大家都知道cmd下是允许用引号的,比如:cd "File Programs"
; 如果你不加引号那么OS会以为File和Programs是两个参数,或者
; 你也可以这样cd File" "Programs,只要不要有空格就可以了
; 所以如果你碰到了"号必然是成双的
; --------------------------------------------------
@@: ;这部分是为了判断引号中的内容,即引号中有空格不会增加参数数目
lodsb
or al, al ;;首先判断该参数是不是最后一个参数即跟了0字符
jz _argc_end
cmp al, CHAR_DELI
jnz @B ;没碰到了第二个引号所以继续判断引号中内容
jmp _argc_loop1 ;引号结束跳回
_argc_end:
popad
mov eax, @dwArgc
ret
_argc endp
;----------------------------------------------
_argv proc,
_dwArgv:dword,
_lpReturn:ptr byte,
_dwSize:dword
local @dwArgv, @dwFlag
; _dwArgv: 所要得到的参数的下标
; _lpReturn: 参数内容的返回地址
; _dwSize: _lpReturn的大小
; @dwArgv: 主要为了确定目前参数是不是_dwArgv所指定的那个
; @dwFlag: 如果是_dwArgv所指定的那个就置位
;-----------------------------------------------
pushad ;保护通用寄存器不用说了
inc _dwArgv ;因为一般下标都是0开始的,这里为了@dwArgv判断方便变为1
mov @dwArgv, 0 ;计数器清除
mov edi, _lpReturn
invoke GetCommandLine ;下面和_argv中的如出一辙的代码不解释
mov esi, eax
cld
_argv_loop:
lodsb ;这一段主要是为了清除一开始那些没用的空格键
or al, al
jz _argv_end
cmp al, CHAR_BLANK
jz _argv_loop
dec esi ;空格清除完毕,碰到第一个字符
inc @dwArgv ;计数器增加,意味着已经是第@dwArgv个参数了
mov @dwFlag, FALSE ;先把标志清空,这个标志用于判断当前参数是不是dwArgv所指定的那个
mov eax, _dwArgv
cmp eax, @dwArgv ;看当前参数是不是所要求的那个
jnz @F
mov @dwFlag, TRUE ;如果是的话那就让标志置位
@@:
_argv_loop1:
lodsb
or al, al
jz _argv_end
cmp al, CHAR_BLANK
jz _argv_loop ;碰到空格,说明这个参数结束,跳回
cmp al, CHAR_DELI ;看是否是"号
jz _argv_loop2 ;跳转至下一个标号来处理"号
cmp _dwSize, 1
;------------------------------------------------------------------
; 这边需要解释一下,这个_dwSize的含义是_lpReturn缓冲区的大小,如果
; 当前参数是所要求的那个,那么就开始把这个参数一个字节一个字节的
; 往缓冲区中填充,并且同时减少_dwSize,这里用cmp _dwSize, 1而不是
; cmp _dwSize, 0是因为字符串最后一个要留着填充空字符来结束字符串
;-------------------------------------------------------------------
jle @F
;如果已经到了缓冲区能容纳的最大字符数但是还没有结束,那么后面的那些全部作废
;不继续stosb装如缓冲区
cmp @dwFlag, TRUE
jne @F
;如果不是所要求的参数那也不继续装入缓冲区
stosb ;装货
dec _dwSize
@@:
jmp _argv_loop1
_argv_loop2:
lodsb
or al, al
jz _argv_end
cmp al, CHAR_DELI ;看有没有第二个"出现,如果出现了说明引号已经结束
jz _argv_loop1 ;跳回
cmp _dwSize, 1
jle @F
cmp @dwFlag, TRUE
jne @F
stosb
dec _dwSize
@@:
jmp _argv_loop2
_argv_end:
xor al, al ;把0字符装入最后一个位置来结束字符串
stosb
popad
ret
_argv endp
到这里这两个过程都已经解释完毕了
接下去就是两个宏了
先贴代码:
reverseArgs MACRO arglist:VARARG
LOCAL txt,count
txt TEXTEQU <>
count = 0
FOR i,<arglist>
count = count + 1
txt TEXTEQU @CatStr(i,<!,>,<%txt>)
ENDM
IF count GT 0
txt SUBSTR txt,1,@SizeStr(%txt)-1
ENDIF
EXITM txt
ENDM
_invoke MACRO _Proc,args:VARARG
LOCAL count
count = 0
% FOR i,< reverseArgs( args ) >
count = count + 1
push i
ENDM
call dword ptr _Proc
ENDM
这两个宏函数的意思非常简单我觉得困扰大家的并不是这个宏过程的含义而是里面的一些伪指令可能不知道什么意思。
我去查了Intel汇编程序设计里面结构与宏的部分, 里面也没有关于这些伪指令的解释
我用的是masm32 SDK包,在masm32\\help\\masm.chm这个文档中有详细解释
首先就是那个VARARG
看里面一堆好像很牛逼,实际上只是换了鸟文
VARARG属性允许你传递一个多个参数至宏中,当被调用的时候宏参数会传递一个逗号分隔的值列表(如果没有给予值那就是空格)
实际意思就是本来只能传一个参数,现在能传好多个。
接着是CATSTR:
这个CATSTR有两种用法,一种是宏函数,一种是直接用这个定义一个这样的变量,就像TEXTEQU和EQU伪指令做的一样
这里只解释代码中的宏函数用法
txt TEXTEQU @CatStr(i,<!,>,<%txt>)
首先解释下!号类似于C语言中的转义符所以<!,>可以直接看成\',\'号就行了,为何加一个<>,因为!,是一个字符,但看上去是两个,实际上也是两个,为了让编译器看的懂,用<>括起来,代表我是一个
%txt的意思是取txt所代表的东西而不是txt本身
这里@CatStr(i, <!,>, <%txt>),意思实际上就是把i和,还有txt所代表东西黏起来。举个例子%txt = <1,> i = 2 ,那么首先把2和,黏起来后变成2,然后黏到%txt前面就变成了<2,1,>。
接下去是SUBSTR:
这里没使用宏函数,使用了定义的方法
txt SUBSTR txt,1,@SizeStr(%txt)-1
最后那个长度参数是不必要的。如果没有指定长度参数 那返回的是所有字符
这个意思就是把txt定义成txt从第一个字符开始的@SizeStr(%txt) - 1个,也就是从屁股后面减掉一个。原因是上面CATSTR得到的结果是1,2, 但是2后面的那个逗号是不必要的
@SizeStr宏显而易见是类似于strlen()函数,做一下搬运工作:
% for i,< reverseArgs( args ) >
可能这句话也十分困扰大家吧,那个%号是用来干什么的?
%原本是展开操作符,为的是把常量表达式转成文本表示形式或者展开文本宏。
显而易见这里是展开文本宏,如果%作为源代码行的第一个字符, 就指示预处理器展开在该行所发现的所有文本宏和宏函数。
那个宏就是reverseArgs(args)
-----------------------------------END--------------------------------------
总结一下:
这两个宏的目的是首先reverseArgs是为了把通过GetCommandLine收到的参数字符串逆置,比如参数是1,2,3那就变成3,2,1
然后在通过_invoke来调用。至于为什么要逆置原因是每个函数都维护着一个帧栈,而参数都是反向压栈,这样的话在最后call的时候可以按顺序出栈,举一个例子:
#include <stdio.h>
int add(int a, int b, int c) {
return a + b + c;
}
void main() {
int a = 5, b = 2, c = 1;
add(a, b, c);
return;
}
实际上是(这里手写,可能和OD反编译出来的不用,但就是这个意思):
push ebp
mov ebp, esp
sub esp, 0Ch
mov dword ptr [ebp], 5
mov dword ptr [ebp - 4], 2
mov dword ptr [ebp - 8], 1
push dword ptr [ebp - 8]
push dword ptr [ebp - 4]
push dword ptr [ebp]
call 0040FC30 ;假设add在0040FC30
mov esp, ebp
pop ebp
retn
0040FC30:
push ebp
mov ebp, esp
xor eax, eax
mov eax, [ebp + 8]
add eax, [ebp + 12]
add eax, [ebp + 16]
mov esp, ebp
pop ebp
retn
完