NASM 内存寻址
NASM Memory Addressing
我正在研究程序命令行参数。特别是我正在尝试对字符串 argv[1] 进行一些测试。如果我使用两步方法获取 argv[1] 的地址,我的代码运行正常。
mov ebx, [ebp+12]
mov eax, [ebx+4] ; address of argv[1]
如果我使用一步,我的程序会打印出乱码。
mov eax, [ebp+16] ; address of argv[1]
我假设这两种方法现在都引用地址 [ebp+16] 是不是不正确?我是否遗漏了一些微不足道的东西?
这就是您问题的答案。
mov eax, [ebp+16]
lea ebx, [ebp+12]
mov eax, [ebx+4]
或
mov eax, [ebp+16]
mov ebx, ebp
add ebx, 12
mov eax, [ebx+4]
前者节省了几个字节的代码,但它们在功能上是等价的。
在汇编中使用指向指针的指针时很容易混淆。
argv
是一个 "array of strings" 或更好的指向 char 的指针数组,因为在 C 中,当作为参数传递时,数组会衰减为指向其项目类型的指针,实际上 argv
它是指向 char 或 char** argv
的指针。
这告诉我们,我们需要两个解引用来访问任何字符串的字符,一个来访问指向任何此类字符串的任何指针。
假设 cdecl 约定,其中参数以相反的顺序在堆栈上传递,并假设设置标准帧指针的标准序言我们有 argc
在 ebp+0ch
.
请注意 ebp
具有指针的语义,因此 ebp+0ch
只是指针算术以获得另一个指针,这次指向 argc
值。
如果我们愿意给 ebp+0ch
一个 C 类型,它将是 char***
,因此需要两次解引用来访问指针 argv[1]
。
将 argv[1]
变成 ESI
的代码是:
;typeof(ebp+0ch) = char***
mov esi, DWORD [ebp+0ch] ;1st defer, esi = argv, typeof(esi) = char**
mov esi, DWORD [esi+04h] ;2nd defer, esi = argv[1], typeof(esi) = char*
;Optional, Get a char
mov al, BYTE [esi] ;3rd defer, al = argv[1][0], typeof(al) = char
类型检查。
听起来很混乱?
让我们画出那些指针!
The stack The memory
100ch | 2000h | argv 2000h | 2008h | argv[0]
1008h | 2 | argc 2004h | 2010h | argv[1]
1004h | yyyyyy | return address 2008h | file | argv[0][0..3]
1000h | xxxxxx | old frame pointer 200ch | .a[=11=][=11=] | argv[0][4..7]
2010h | -arg | argv[1][0..3]
EBP = 1000h 2014h | 1[=11=][=11=][=11=] | argv[1][4..7]
ebp+0ch
是 1000h + 0ch = 100ch,它是 argv
值的地址。
mov esi, DWORD [ebp+0ch]
类似于 mov esi, DWORD [100ch]
并将 ESI
设置为 2000h。
2000h 是 argv
的值,它是一个数组,所以它是 argv[0]
的地址。
argv[1]
的地址提前四个字节,因此2000h+04h = 2004h。
mov esi, DWORD [esi+04h]
类似于 mov esi, DWORD [2004h]
并将 ESI
设置为 2010h。
2010h 是字符串“-arg1”的地址。
请注意,上图不符合 C 或 C++ 标准,因为 argv[argc]
必须为 0。
我把它排除在外了。
我正在研究程序命令行参数。特别是我正在尝试对字符串 argv[1] 进行一些测试。如果我使用两步方法获取 argv[1] 的地址,我的代码运行正常。
mov ebx, [ebp+12]
mov eax, [ebx+4] ; address of argv[1]
如果我使用一步,我的程序会打印出乱码。
mov eax, [ebp+16] ; address of argv[1]
我假设这两种方法现在都引用地址 [ebp+16] 是不是不正确?我是否遗漏了一些微不足道的东西?
这就是您问题的答案。
mov eax, [ebp+16]
lea ebx, [ebp+12]
mov eax, [ebx+4]
或
mov eax, [ebp+16]
mov ebx, ebp
add ebx, 12
mov eax, [ebx+4]
前者节省了几个字节的代码,但它们在功能上是等价的。
在汇编中使用指向指针的指针时很容易混淆。
argv
是一个 "array of strings" 或更好的指向 char 的指针数组,因为在 C 中,当作为参数传递时,数组会衰减为指向其项目类型的指针,实际上 argv
它是指向 char 或 char** argv
的指针。
这告诉我们,我们需要两个解引用来访问任何字符串的字符,一个来访问指向任何此类字符串的任何指针。
假设 cdecl 约定,其中参数以相反的顺序在堆栈上传递,并假设设置标准帧指针的标准序言我们有 argc
在 ebp+0ch
.
请注意 ebp
具有指针的语义,因此 ebp+0ch
只是指针算术以获得另一个指针,这次指向 argc
值。
如果我们愿意给 ebp+0ch
一个 C 类型,它将是 char***
,因此需要两次解引用来访问指针 argv[1]
。
将 argv[1]
变成 ESI
的代码是:
;typeof(ebp+0ch) = char***
mov esi, DWORD [ebp+0ch] ;1st defer, esi = argv, typeof(esi) = char**
mov esi, DWORD [esi+04h] ;2nd defer, esi = argv[1], typeof(esi) = char*
;Optional, Get a char
mov al, BYTE [esi] ;3rd defer, al = argv[1][0], typeof(al) = char
类型检查。
听起来很混乱?
让我们画出那些指针!
The stack The memory
100ch | 2000h | argv 2000h | 2008h | argv[0]
1008h | 2 | argc 2004h | 2010h | argv[1]
1004h | yyyyyy | return address 2008h | file | argv[0][0..3]
1000h | xxxxxx | old frame pointer 200ch | .a[=11=][=11=] | argv[0][4..7]
2010h | -arg | argv[1][0..3]
EBP = 1000h 2014h | 1[=11=][=11=][=11=] | argv[1][4..7]
ebp+0ch
是 1000h + 0ch = 100ch,它是 argv
值的地址。
mov esi, DWORD [ebp+0ch]
类似于 mov esi, DWORD [100ch]
并将 ESI
设置为 2000h。
2000h 是 argv
的值,它是一个数组,所以它是 argv[0]
的地址。
argv[1]
的地址提前四个字节,因此2000h+04h = 2004h。
mov esi, DWORD [esi+04h]
类似于 mov esi, DWORD [2004h]
并将 ESI
设置为 2010h。
2010h 是字符串“-arg1”的地址。
请注意,上图不符合 C 或 C++ 标准,因为 argv[argc]
必须为 0。
我把它排除在外了。