所以,我对跳转指令在操作系统中的工作原理感到困惑。我认为跳转指令设置处理器程序计数器中的值。但程序可以在内存中的不同位置运行。我在x86中看到,有JMP EAX
指令,但我的C ++代码似乎没有使用它。我在VC ++中编译了一些C ++代码:
int main()
{
int i = 0;
while (i < 10)
{
++i;
if (i == 7)
{
i += 1;
continue;
}
}
}
这转换为:
int main()
{
00411370 push ebp
00411371 mov ebp,esp
00411373 sub esp,0CCh
00411379 push ebx
0041137A push esi
0041137B push edi
0041137C lea edi,[ebp-0CCh]
00411382 mov ecx,33h
00411387 mov eax,0CCCCCCCCh
0041138C rep stos dword ptr es:[edi]
int i = 0;
0041138E mov dword ptr [i],0
while (i < 10)
00411395 cmp dword ptr [i],0Ah
00411399 jge main+47h (4113B7h)
{
++i;
0041139B mov eax,dword ptr [i]
0041139E add eax,1
004113A1 mov dword ptr [i],eax
if (i == 7)
004113A4 cmp dword ptr [i],7
004113A8 jne main+45h (4113B5h)
{
i += 1;
004113AA mov eax,dword ptr [i]
004113AD add eax,1
004113B0 mov dword ptr [i],eax
continue;
004113B3 jmp main+25h (411395h)
}
}
004113B5 jmp main+25h (411395h)
}
004113B7 xor eax,eax
004113B9 pop edi
004113BA pop esi
004113BB pop ebx
004113BC mov esp,ebp
004113BE pop ebp
004113BF ret
所以我很困惑,对于命令jmp 411395h
,这是否暗示程序总是加载到内存中的同一位置?因为那似乎不合逻辑。
答案 0 :(得分:6)
不,这里有两件事可以发挥作用 - 你没有指定操作系统,所以我会给出一般答案。
首先,可执行文件很少采用最终格式。作为简化,编译将源转换为目标文件,链接将目标文件组合成可执行文件。
但是可执行文件必须加载到内存中,在那个阶段,可以进行更多修改。其中一个修改可能是修复可执行文件中的内存引用,以指向已在不同位置加载的内存。
这可以通过可执行文件来实现,该文件包含需要在运行时修复的地址列表。
在许多现代操作系统中,虚拟内存和物理内存之间也存在脱节。
当您的流程启动时,您将获得自己的(我相信的是Windows 32位的4G)地址空间,您的流程将加载到该地址空间。此地址空间中的地址与您的实际物理内存地址几乎没有关系,两者之间的转换由内存管理单元(MMU)完成。
事实上,您的进程可能在整个物理地址空间中飞出,因为它已经被分页。但虚拟地址不会改变。
答案 1 :(得分:6)
正如其他人写的那样,有相对跳转和相对调用指令,它们基本上将固定值添加到eip
,因此不依赖于程序在内存中的位置;编译器喜欢尽可能使用这些。您可以查看代码字节以查看编译器使用的确切指令。但是,我假设你在询问跳转/调用绝对地址。
当链接器生成可执行文件时,它会生成假定特定base address的绝对地址; Microsoft链接器通常使用400000h
。当OS加载可执行文件或dll时,它通过添加实际加载可执行文件的地址与链接器基于它的地址之间的差异来“修复”所有绝对地址。除.com
之外的所有可执行格式都指定了某种修正表,其中列出了必须以这种方式修补的可执行文件中的所有位置。因此,在操作系统将可执行文件加载到基址的内存中后,例如1500000h
,您的跳转将看起来像jmp 1511395h
。您可以通过使用调试器查看实际代码字节来检查这一点。
较旧的Windows系统倾向于在链接器使用的基址处加载可执行文件;这造成了安全风险,因为攻击者会提前知道内存中的内容。这就是为什么较新的系统使用基地址随机化的原因。
答案 2 :(得分:3)
内存位置与进程相关。相对于程序的开头,main
总是在内存中的相同位置。
答案 3 :(得分:3)
没有。在x86(以及其他体系结构)上,大多数跳转指令都是 IP-relative :指令的二进制机器代码表示与当前指令指针的偏移量。因此,无论代码加载到哪个虚拟地址,跳转指令都能正常运行。
答案 4 :(得分:3)
相对跳转取当前机器指令的地址(称为指令指针)并添加偏移量来计算要跳转到的地址。
如果你看一下你的代码
004113B3 jmp main+25h (411395h)
004113B5 jmp main+25h (411395h)
004113B7 xor eax,eax
你会注意到jmp指令长2个字节(jmp为1个字节,偏移为1个字节),并且不能存储绝对的4字节地址。
相对跳转是CPU的基本功能(据我所知65xx,Z80,8086,68000),与虚拟内存,内存映射或地址空间随机化等高级功能无关。
答案 5 :(得分:2)
大多数芯片都有相对跳跃(相对于当前位置)和虚拟寻址。
答案 6 :(得分:0)
int main()
{
00411370 push ebp
00411371 mov ebp,esp
00411373 sub esp,0CCh
00411379 push ebx
0041137A push esi
0041137B push edi
0041137C lea edi,[ebp-0CCh]
00411382 mov ecx,33h
00411387 mov eax,0CCCCCCCCh
0041138C rep stos dword ptr es:[edi]
int i = 0,int j=0;
0041138E mov dword ptr [i][j],0
while (i < 10)
00411395 cmp dword ptr [i][j[,0Bh
00411399 jge main+47h (4113B7h)
{
++i;
0041139B mov eax,dword ptr [i][j]
0041139E add eax,1
004113A1 mov dword ptr [i][j],eax '
if (i == 7)
004113A4 cmp dword ptr [i][j],7
004113A8 jne main+45h (4113B5h)
{
i += 1;
004113AA mov eax,ebx,dword ptr [i][j]
004113AD add eax,1
004113B0 mov dword ptr [i][j],ebx
continue;
004113B3 jmp main+25h (411395h)
}
}
004113B5 jmp main+25h (411395h)
}
004113B7 xor eax,ebx
004113B9 pop edi
004113BA pop esi
004113BB pop ecx
004113BC mov esp,ebp
004113BE pop ebp
004113BF ret