MASM 中的指针有什么意义?
What's the point of the pointer in MASM?
我正在学习Assembly Language For Intel-Based Computers
,有一点让我很困惑。
众所周知,我们可以使用任何通用寄存器来寻址,像这样:
.data
array1 byte 1,2,3,4
.code
mov esi,offset array1
mov al,[esi] ; al=1
mov al,[esi+1] ; al=2
mov al,[esi+2] ; al=3
mov al,[esi+3] ; al=4
[esi+idata]
(idata是立即数)就像指针一样,够强够用
但是这本书告诉我如何键入定义这样的指针:
pbyte tpyedef ptr byte
.data
array1 byte 1,2,3,4
ptr1 pbyte array
.code
mov esi,ptr1
mov al,[esi] ; al=1
mov al,[esi+1] ; al=2
mov al,[esi+2] ; al=3
mov al,[esi+3] ; al=4
所以真正让我感到困惑的是,既然 array1
是指向数据的指针,那么 ptr1
有什么意义呢?指针也需要4个字节来存储。
mov esi,ptr1
不等于mov esi,offset array1
吗?
此外,本书还给出了不同类型的指针,例如:
pbyte tpyedef ptr byte
pbyte tpyedef ptr word
pbyte tpyedef ptr dword
pbyte tpyedef ptr qword
指针不就是指向内存中的一个字节吗?有什么不同?我试过这样:
pt typedef ptr qword
.data
array byte 1,2,3,4,5,6,7,8
arrptr pt array
.code
mov esi,arrptr
mov eax,[esi]
我在 eax 中得到 04030201,一个双字类型,而不是一个四字。
综上所述,我想知道指针在MASM中的存在是不是很有必要。或者有什么指针可以但 [esi+idata]
不能的东西吗?
Isn't mov esi,ptr1 equal to mov esi,offset array1?
请注意 mov esi, ptr1
实际上意味着 mov esi, DWORD PTR [ptr1]
,这是一个重要的区别。
比mov esi, OFFSET array
还要间接一个级别。
简单地说 - 如果 array
位于 0x1000,那么 mov esi, OFFSET array
只是 mov esi, 1000h
的可读版本(实际执行的指令)。
而如果ptr1
在0x1010,则mov esi, ptr1
被组装成mov esi, DWORD PTR [1010h]
- 读取0x1010处DWORD的内容存入ESI。
为了使两条指令具有相同的行为,0x1010 处的 DWORD 必须为 0x1000(即指向 array
)。
但是,它也可以指向任何其他数组。
在您的示例中,它几乎没有用,不是因为您只有一个数组,而是因为数据是静态分配的-因此您始终可以使用 OFFSET
.
获取其地址
假设您想丢弃字符串的第一个单词,过程可以将指针作为参数(如果数据是静态分配的,可以用 OFFSET
初始化)和 return 指向第一个的指针非丢弃词
+--- Original pointer, initialized with OFFSET
|
v
Hello world from SO!
^
|
+--- Pointer returned
您可以将结果保存到一个名为 ptr1
的变量中。
现在如果你想重复这个过程,你需要使用 ptr1
的值作为过程的输入。
没有 OFFSET
会做,因为指针的值不能在汇编程序时推导出来。
Doesn't the pointer just point one byte in the memory? What's the difference?
一般来说,为汇编程序提供比所需更多的信息会派上用场:
- 记录代码
- 让它执行一些初始化
- 让它执行一些检查
知道变量是指针可以帮助未来的读者。
TYPEDEF PTR
can be used to specify near and far pointers,但这与任何现代 OS.
设置的保护模式无关
远指针的初始化方式与近指针不同。
也许 MASM 对指针和指针对象的类型进行了一些检查?我对此表示怀疑,但我确实不知道。
正如指出的:
MASM does in fact (unlike other assemblers) have rudimentary type checking on pointers to data that have been explicitly defined and then directly referenced.
And I get 04030201 in eax, a dword type, not a qword.
目标是 32 位,因此源不能是 64 位。
如果程序员显式使用大小(例如使用寄存器名称或使用 WORD PTR [...]
),则隐式大小将被覆盖。
Is there something that the pointer can but [esi+idata]
can't?
不,任何高级代码功能最终都会组装到 ISA 指令中,因此通过使用它们,您可以模拟任何高级功能。
请注意,某些指令在元级别起作用,例如 .data
。
这些不能被模拟,因为它们不只是生成代码。
在 ASM 中,您实际上需要 typedef 语句来开发应用程序。实际上,它们只不过是注释文本。如果您正在使用 FPU,则有一些真正的内部格式需要学习,但对于标准的 32 位 ASM,您实际上只需要知道某些内容的大小,即它是 BYTE、WORD 还是 DWORD。当然,如果您正在与将 MSB 视为符号位的库进行交互,或者您需要自己使用带符号的值,请记住这一点并使用 jge 而不是 jae 等。但这不是火箭科学。
大问题: 这本书是一本您需要通读的教育文本吗,因此会根据您坚持其方法的程度来评分?
如果是,对不起,伙计,咬紧牙关开始背这些东西。
如果不是,我认为它可能是由用 C 思考的人写的,只是将他们的 C-centric 想法翻译成 ASM。 (不是犯罪——可能是一个学习 C 的好人:)
你明明只是在学习,但你问的问题非常好。
So what realy confuses me is that since the array1 is a pointer to the data, what's the point of ptr1?
对于您的示例代码,有 none。由于 ptr1 上的 DWORD 被初始化为 OFFSET array1 然后从未改变。
但是 are 很多时候你会想要维护一个指向 list/array 等的全局指针。如果你每次进入该部分时都从数组的开头开始代码,然后正如您指出的那样,您可以使用 OFFSET ...简单。但是,如果您的任务性质要求您在下次返回时必须记住自己的位置,那么您需要将该位置存储在某个地方;因此指针。
The pointer also requires 4 bytes to store.
当然可以,但不要太在意指针 space,如果指针是完成任务的正确方法,那么 4 个字节不算什么。如果是我,我会声明为:
.data
lpThisArray dd OFFSET array1
正因为如此,它是一个 DWORD。它是 32 iddy-biddy 个晶体管,它们在任何给定的瞬间单独地举办派对或睡觉。他们既不知道也不关心他们持有的是数组指针、游标 Y co-ord、您的表亲单元格编号还是单词 "TWIT"。他们有自己的生活,他们不关心我们的问题……但没关系!我们在这里不是盲目飞行。我们称之为 "lpThisArray" 我非常有信心我不会不小心拨通它并试图让他在下班途中来接我一些东西。我在家里也一样聪明,我几乎总是记得不要把餐具插在电源插座上——当你用心去做时,你能做的事情真是太神奇了!
在 32 位领域,几乎不需要使用 db、dw 或 dd(= BYTE、WORD 或 DWORD)以外的任何东西来声明标准 numeric/string/pointer 等东西,因为最终这就是一切否则归结为无论如何。 FPU/SSE 等等,除了一切都是 8 位、16 位或 32 位。
当然,结构定义和语法非常方便,所以如果您正在使用 API 结构(或构建您自己的大结构),使用它们是有意义的。
MyOfn OPENFILENAME <>
无论如何打字都非常容易 ;)
...只要你明白:
.data?
csrPos POINT <>
.code
invoke GetCursorPos,OFFSET csrPos
mov ebx,csrPos.y
在各个方面都与以下内容相同:
.data?
csrX dd ?
csrY dd ?
.code
push OFFSET csrX
call GetCursorPos
mov ebx,csrY
它们将编译成相同的可执行文件。
Isn't mov esi,ptr1 equal to mov esi,offset array1?
撇开我个人不喜欢作者不必要地深奥地选择数据大小声明,答案是肯定的,只有当 ptr1 指向数组的开头时。 ptr1定义了一个DWORD位置,可以容纳任意32位的值,但是OFFSET array1是一个固定值,它是数组第一个字节的地址。
好的...适合初学者的 OFFSET。好点子。可能最好首先考虑它之前的“.data”。
与高级语言中的声明不同,“.data”不是一些方便的 human-centric 标题,您可以在该标题下列出您想要玩的数字。在 ASM 中,它定义了 exe 文件中物理部分的开始,系统加载程序将其映射到进程虚拟地址 space 中的特定地址。从程序编译开始,其中的所有内容都被定义为位于特定的预定位置。 (理论上它们可以是 re-based 但实际上它们几乎从来不是,而且这与此无关)。
习惯了高级语言的人假设当程序运行时,它会到达找到这个 "OFFSET" 的人,他迅速跑开并找到数组然后报告回来它的位置。事实上,OFFSET 是一个编译器指令,它将导致编译器 hard-code 所提供标签的实际内存地址直接进入程序的操作码。在本例中,array1 标记了数组中第一个字节的位置。 "OFFSET" 恰好定义了一个且唯一的位置,该位置在执行程序的生命周期内永远不会改变有能力的。在这种情况下,您可以将 ptr1 视为一个变量,因此它可以保存任何 32 位值,并且该值每秒可以更改 1000 次。
数据标签
这对于解释指针点很方便,但实际上它是不准确的。 "ptr1"其实就是一个标签。它在 .data 部分标记了一个特定位置,并且在程序的生命周期内也是绝对固定的。该位置的 4 个字节保存指针的值。
例如:
mov ptr1,12345
;( then later)
mov esi,OFFSET ptr1
mov eax,[esi]
; eax now holds 12345
OFFSET 可以接受 ptr1 作为参数这一事实意味着它必须被修复。数据标签定义位置就像任何其他标签一样,区别只是语法约定之一,因为数据标签应用了 隐含的取消引用 。 "ptr1" 就像所有其他标签一样,实际上是 OFFSET 的结果,因为它编译为常量 32 位立即值,但作为数据标签,当我们编码 "ptr1" 时,MASM 编译“[ptr1]”。这使人们更容易将数据标签视为变量并以此使用它们,但它也确实会掩盖实际发生的事情。
关于数据标签的另一件事是它们有一个大小(不是 类型)。 这是 MASM 的特性,许多人(有时包括我自己;)往往错误地称为一种类型检查形式。虽然在这一点几乎成了哲学讨论,MASM 确实跟踪了所有实际可以确定的大小,这使用户不必输入 "DWORD ptr" "BYTE ptr" 或 "WORD ptr" 广告恶心几乎每条指令。这不是必需的,因为编译器已经知道几乎所有内容的大小。
在我看来,这在很大程度上是作为时间 saving/productivity 增加的功能实现的,但是 by-product 是当编译器尝试处理涉及两个不同大小定义的指令时,它不知道哪个是正确的,因此不知道应该生成哪个操作码,因此无法继续。唯一让我恼火的是,我觉得我们应该能够通过显式声明一个 SIZE ptr 来覆盖它,而不必求助于用有问题的数据标签的偏移量加载一个寄存器并取消引用它从而摆脱附加尺寸声明。
Doesn't the pointer just point one byte in the memory?
是的..有点。指针只是一个数字,它指向一个 address 多于一个 byte,因为使用 32 位处理器,您可以读取或写入 1一次 2 或 4 个字节 to/from 该地址。
pt typedef ptr qword
.data
array byte 1,2,3,4,5,6,7,8
arrptr pt array
.code
mov esi,arrptr
mov eax,[esi]
这只是由于过度消费我们将成为普利策奖得主的产品而造成的一点混乱....
实际上你已经这样做了:
.data
array db 1,2,3,4,5,6,7,8
arrptr dd OFFSET array
.code
mov esi,arrptr
mov eax,[esi]
这是一种相当迂回的方式:
.data
array db 1,2,3,4,5,6,7,8
.code
mov eax,OFFSET array
mov eax,[eax]
因为您已经将一个值加载到 eax 中,其源是一个取消引用的指针,任何可能附加到以前保存指针的容器的大小定义现在都已不复存在。对于这个 mov 指令,数据大小由目标操作数定义,即 eax 本身。由于eax是一个32bit(4字节)的寄存器,所以已经加载了4字节。
And I get 04030201 in eax, a dword type, not a qword
我恳求你,如果你想出如何将 qword 放入 eax,请告诉我诀窍 ;)
我正在学习Assembly Language For Intel-Based Computers
,有一点让我很困惑。
众所周知,我们可以使用任何通用寄存器来寻址,像这样:
.data
array1 byte 1,2,3,4
.code
mov esi,offset array1
mov al,[esi] ; al=1
mov al,[esi+1] ; al=2
mov al,[esi+2] ; al=3
mov al,[esi+3] ; al=4
[esi+idata]
(idata是立即数)就像指针一样,够强够用
但是这本书告诉我如何键入定义这样的指针:
pbyte tpyedef ptr byte
.data
array1 byte 1,2,3,4
ptr1 pbyte array
.code
mov esi,ptr1
mov al,[esi] ; al=1
mov al,[esi+1] ; al=2
mov al,[esi+2] ; al=3
mov al,[esi+3] ; al=4
所以真正让我感到困惑的是,既然 array1
是指向数据的指针,那么 ptr1
有什么意义呢?指针也需要4个字节来存储。
mov esi,ptr1
不等于mov esi,offset array1
吗?
此外,本书还给出了不同类型的指针,例如:
pbyte tpyedef ptr byte
pbyte tpyedef ptr word
pbyte tpyedef ptr dword
pbyte tpyedef ptr qword
指针不就是指向内存中的一个字节吗?有什么不同?我试过这样:
pt typedef ptr qword
.data
array byte 1,2,3,4,5,6,7,8
arrptr pt array
.code
mov esi,arrptr
mov eax,[esi]
我在 eax 中得到 04030201,一个双字类型,而不是一个四字。
综上所述,我想知道指针在MASM中的存在是不是很有必要。或者有什么指针可以但 [esi+idata]
不能的东西吗?
Isn't mov esi,ptr1 equal to mov esi,offset array1?
请注意 mov esi, ptr1
实际上意味着 mov esi, DWORD PTR [ptr1]
,这是一个重要的区别。
比mov esi, OFFSET array
还要间接一个级别。
简单地说 - 如果 array
位于 0x1000,那么 mov esi, OFFSET array
只是 mov esi, 1000h
的可读版本(实际执行的指令)。
而如果ptr1
在0x1010,则mov esi, ptr1
被组装成mov esi, DWORD PTR [1010h]
- 读取0x1010处DWORD的内容存入ESI。
为了使两条指令具有相同的行为,0x1010 处的 DWORD 必须为 0x1000(即指向 array
)。
但是,它也可以指向任何其他数组。
在您的示例中,它几乎没有用,不是因为您只有一个数组,而是因为数据是静态分配的-因此您始终可以使用 OFFSET
.
获取其地址
假设您想丢弃字符串的第一个单词,过程可以将指针作为参数(如果数据是静态分配的,可以用 OFFSET
初始化)和 return 指向第一个的指针非丢弃词
+--- Original pointer, initialized with OFFSET
|
v
Hello world from SO!
^
|
+--- Pointer returned
您可以将结果保存到一个名为 ptr1
的变量中。
现在如果你想重复这个过程,你需要使用 ptr1
的值作为过程的输入。
没有 OFFSET
会做,因为指针的值不能在汇编程序时推导出来。
Doesn't the pointer just point one byte in the memory? What's the difference?
一般来说,为汇编程序提供比所需更多的信息会派上用场:
- 记录代码
- 让它执行一些初始化
- 让它执行一些检查
知道变量是指针可以帮助未来的读者。
TYPEDEF PTR
can be used to specify near and far pointers,但这与任何现代 OS.
设置的保护模式无关
远指针的初始化方式与近指针不同。
也许 MASM 对指针和指针对象的类型进行了一些检查?我对此表示怀疑,但我确实不知道。
正如
MASM does in fact (unlike other assemblers) have rudimentary type checking on pointers to data that have been explicitly defined and then directly referenced.
And I get 04030201 in eax, a dword type, not a qword.
目标是 32 位,因此源不能是 64 位。
如果程序员显式使用大小(例如使用寄存器名称或使用 WORD PTR [...]
),则隐式大小将被覆盖。
Is there something that the pointer can but
[esi+idata]
can't?
不,任何高级代码功能最终都会组装到 ISA 指令中,因此通过使用它们,您可以模拟任何高级功能。
请注意,某些指令在元级别起作用,例如 .data
。
这些不能被模拟,因为它们不只是生成代码。
在 ASM 中,您实际上需要 typedef 语句来开发应用程序。实际上,它们只不过是注释文本。如果您正在使用 FPU,则有一些真正的内部格式需要学习,但对于标准的 32 位 ASM,您实际上只需要知道某些内容的大小,即它是 BYTE、WORD 还是 DWORD。当然,如果您正在与将 MSB 视为符号位的库进行交互,或者您需要自己使用带符号的值,请记住这一点并使用 jge 而不是 jae 等。但这不是火箭科学。
大问题: 这本书是一本您需要通读的教育文本吗,因此会根据您坚持其方法的程度来评分?
如果是,对不起,伙计,咬紧牙关开始背这些东西。
如果不是,我认为它可能是由用 C 思考的人写的,只是将他们的 C-centric 想法翻译成 ASM。 (不是犯罪——可能是一个学习 C 的好人:)
你明明只是在学习,但你问的问题非常好。
So what realy confuses me is that since the array1 is a pointer to the data, what's the point of ptr1?
对于您的示例代码,有 none。由于 ptr1 上的 DWORD 被初始化为 OFFSET array1 然后从未改变。 但是 are 很多时候你会想要维护一个指向 list/array 等的全局指针。如果你每次进入该部分时都从数组的开头开始代码,然后正如您指出的那样,您可以使用 OFFSET ...简单。但是,如果您的任务性质要求您在下次返回时必须记住自己的位置,那么您需要将该位置存储在某个地方;因此指针。
The pointer also requires 4 bytes to store.
当然可以,但不要太在意指针 space,如果指针是完成任务的正确方法,那么 4 个字节不算什么。如果是我,我会声明为:
.data
lpThisArray dd OFFSET array1
正因为如此,它是一个 DWORD。它是 32 iddy-biddy 个晶体管,它们在任何给定的瞬间单独地举办派对或睡觉。他们既不知道也不关心他们持有的是数组指针、游标 Y co-ord、您的表亲单元格编号还是单词 "TWIT"。他们有自己的生活,他们不关心我们的问题……但没关系!我们在这里不是盲目飞行。我们称之为 "lpThisArray" 我非常有信心我不会不小心拨通它并试图让他在下班途中来接我一些东西。我在家里也一样聪明,我几乎总是记得不要把餐具插在电源插座上——当你用心去做时,你能做的事情真是太神奇了!
在 32 位领域,几乎不需要使用 db、dw 或 dd(= BYTE、WORD 或 DWORD)以外的任何东西来声明标准 numeric/string/pointer 等东西,因为最终这就是一切否则归结为无论如何。 FPU/SSE 等等,除了一切都是 8 位、16 位或 32 位。
当然,结构定义和语法非常方便,所以如果您正在使用 API 结构(或构建您自己的大结构),使用它们是有意义的。
MyOfn OPENFILENAME <>
无论如何打字都非常容易 ;)
...只要你明白:
.data?
csrPos POINT <>
.code
invoke GetCursorPos,OFFSET csrPos
mov ebx,csrPos.y
在各个方面都与以下内容相同:
.data?
csrX dd ?
csrY dd ?
.code
push OFFSET csrX
call GetCursorPos
mov ebx,csrY
它们将编译成相同的可执行文件。
Isn't mov esi,ptr1 equal to mov esi,offset array1?
撇开我个人不喜欢作者不必要地深奥地选择数据大小声明,答案是肯定的,只有当 ptr1 指向数组的开头时。 ptr1定义了一个DWORD位置,可以容纳任意32位的值,但是OFFSET array1是一个固定值,它是数组第一个字节的地址。
好的...适合初学者的 OFFSET。好点子。可能最好首先考虑它之前的“.data”。
与高级语言中的声明不同,“.data”不是一些方便的 human-centric 标题,您可以在该标题下列出您想要玩的数字。在 ASM 中,它定义了 exe 文件中物理部分的开始,系统加载程序将其映射到进程虚拟地址 space 中的特定地址。从程序编译开始,其中的所有内容都被定义为位于特定的预定位置。 (理论上它们可以是 re-based 但实际上它们几乎从来不是,而且这与此无关)。
习惯了高级语言的人假设当程序运行时,它会到达找到这个 "OFFSET" 的人,他迅速跑开并找到数组然后报告回来它的位置。事实上,OFFSET 是一个编译器指令,它将导致编译器 hard-code 所提供标签的实际内存地址直接进入程序的操作码。在本例中,array1 标记了数组中第一个字节的位置。 "OFFSET" 恰好定义了一个且唯一的位置,该位置在执行程序的生命周期内永远不会改变有能力的。在这种情况下,您可以将 ptr1 视为一个变量,因此它可以保存任何 32 位值,并且该值每秒可以更改 1000 次。
数据标签 这对于解释指针点很方便,但实际上它是不准确的。 "ptr1"其实就是一个标签。它在 .data 部分标记了一个特定位置,并且在程序的生命周期内也是绝对固定的。该位置的 4 个字节保存指针的值。
例如:
mov ptr1,12345
;( then later)
mov esi,OFFSET ptr1
mov eax,[esi]
; eax now holds 12345
OFFSET 可以接受 ptr1 作为参数这一事实意味着它必须被修复。数据标签定义位置就像任何其他标签一样,区别只是语法约定之一,因为数据标签应用了 隐含的取消引用 。 "ptr1" 就像所有其他标签一样,实际上是 OFFSET 的结果,因为它编译为常量 32 位立即值,但作为数据标签,当我们编码 "ptr1" 时,MASM 编译“[ptr1]”。这使人们更容易将数据标签视为变量并以此使用它们,但它也确实会掩盖实际发生的事情。
关于数据标签的另一件事是它们有一个大小(不是 类型)。 这是 MASM 的特性,许多人(有时包括我自己;)往往错误地称为一种类型检查形式。虽然在这一点几乎成了哲学讨论,MASM 确实跟踪了所有实际可以确定的大小,这使用户不必输入 "DWORD ptr" "BYTE ptr" 或 "WORD ptr" 广告恶心几乎每条指令。这不是必需的,因为编译器已经知道几乎所有内容的大小。
在我看来,这在很大程度上是作为时间 saving/productivity 增加的功能实现的,但是 by-product 是当编译器尝试处理涉及两个不同大小定义的指令时,它不知道哪个是正确的,因此不知道应该生成哪个操作码,因此无法继续。唯一让我恼火的是,我觉得我们应该能够通过显式声明一个 SIZE ptr 来覆盖它,而不必求助于用有问题的数据标签的偏移量加载一个寄存器并取消引用它从而摆脱附加尺寸声明。
Doesn't the pointer just point one byte in the memory?
是的..有点。指针只是一个数字,它指向一个 address 多于一个 byte,因为使用 32 位处理器,您可以读取或写入 1一次 2 或 4 个字节 to/from 该地址。
pt typedef ptr qword .data array byte 1,2,3,4,5,6,7,8 arrptr pt array .code mov esi,arrptr mov eax,[esi]
这只是由于过度消费我们将成为普利策奖得主的产品而造成的一点混乱....
实际上你已经这样做了:
.data
array db 1,2,3,4,5,6,7,8
arrptr dd OFFSET array
.code
mov esi,arrptr
mov eax,[esi]
这是一种相当迂回的方式:
.data
array db 1,2,3,4,5,6,7,8
.code
mov eax,OFFSET array
mov eax,[eax]
因为您已经将一个值加载到 eax 中,其源是一个取消引用的指针,任何可能附加到以前保存指针的容器的大小定义现在都已不复存在。对于这个 mov 指令,数据大小由目标操作数定义,即 eax 本身。由于eax是一个32bit(4字节)的寄存器,所以已经加载了4字节。
And I get 04030201 in eax, a dword type, not a qword
我恳求你,如果你想出如何将 qword 放入 eax,请告诉我诀窍 ;)