linux - 在 Linux 上的 64 位进程中运行 32 位代码 - 内存访问

标签 linux assembly x86-64

我正在尝试在 64 位 Linux 进程中运行 32 位代码。 32 位代码是完全独立的,它自己直接进行 IA32 系统调用。如果我在 32 位进程中加载​​此代码,它会运行得很好。

最初,我以为我可以为 32 位代码分配一个堆栈,切换到它,一切都会正常工作,但事情并不顺利。主要是因为与堆栈相关的指令 (POP/PUSH/...) 正在执行 8 字节移位而不是 4 字节。

通过谷歌搜索,我了解到可以通过切换到段选择器 0x23 来转换到 32 位模式。不幸的是,我对分段知之甚少。

我可以用这样的东西(内联 AT&T 汇编)转换到 32 位模式:

movl $0x23, 4(%%rsp) // segment selector 0x23
movq %0, %%rax
movl %%eax, (%%rsp) // target 32-bit address to jump to
lret

其中 %0 包含代码映射位置的 32 位地址。代码开始运行,我可以看到 PUSH/POP 现在按其应有的方式工作,但它崩溃的时间甚至比我在 64 位模式下按看似无害的指令运行代码时更早:

0x8fe48201      mov    0xa483c(%rbx),%ecx

%rbx(或者更像 %ebx,因为这段代码已经是 32 位的,GDB 只是不知道)包含 0x8fe48200。它试图从中读取的地址 (0x8feeca3c) 是有效且可读的(根据 /proc/XXX/maps),当我从 GDB 中读取它时,它包含期望值。

然而,Linux 发送一个 SIGSEGV 到这个指令的进程,错误地址是 0(如 stracep $_siginfo._sifields._sigfault.si_addrgdb 中)。不知何故,0x8feeca3c 似乎不是 32 位领域中的有效地址。

有什么想法可以继续吗?

更新:我写了一个最小的例子,它读取地址 0 时出现段错误,尽管地址 0 并没有真正被引用。似乎读取内存中的任何地址都失败(甚至读取刚刚执行的指令的地址!),尽管堆栈操作工作正常。

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

// 32-bit code we're executing
const unsigned char instructions[] = {
        0x6a, 0, // push 0
        0x58, // popl %eax
        0xe8, 0, 0, 0, 0, // call the next line to get our location in memory
        0x5b, // pop %ebx
        // THE FOLLOWING mov SEGFAULTS, but it is well within the mapped area (which has size 0x3000)
        // A simpler "mov (%ebx), %eax" (0x8b, 0x03) would fail as well
        0x8b, 0x83, 0, 0x20, 0, 0, // mov 0x2000(%ebx), %eax
        0xf4 // hlt, not reached
};

int main()
{
        void* area;
        void* stack;

        area = mmap(NULL, 3*4096, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
        memcpy(area, instructions, sizeof(instructions));

        stack = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
        stack = (void*) (((uint64_t) stack) + 4096 - 4);

        memset(((char*)area) + 2*4096, 0xab, 100); // Place 0xAB in the area we mov from in 32-bit instructions

        // Switch to 32-bit mode and jump into the code
        __asm__ volatile("movq %1, %%rsp;" \
                         "subq $8, %%rsp;" \
                         "movl $0x23, 4(%%rsp);" \
                         "movq %0, %%rax;" \
                         "movl %%eax, (%%rsp);" \
                         "lret" :: "m"(area), "r"(stack) :);
}

最佳答案

好问题:)

问题是 ds 仍然设置为零,在 64 位模式下它没有被使用。所以,你需要重新加载它,它会工作。将初始测试 push/pop 更改为 push $0x2b; pop %ds 就可以了:

const unsigned char instructions[] = {
        0x6a, 0x2b, // push $0x2b
        0x1f, // pop %ds

我从一个 32 位程序中提取了 0x2b 值。我一直想知道为什么 push 有效。仔细观察,ss 也设置为 64 位模式,因此将其复制到 dses 可能更安全。

关于linux - 在 Linux 上的 64 位进程中运行 32 位代码 - 内存访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41921711/

相关文章:

linux -/usr/lib64/libc.so是怎么生成的?

assembly - 用汇编语言画线

c - gcc - 独立于操作系统的函数标签

c - 汇编指令阅读,leaq

c - 有效地找到大数组中的最低有效位?

从 linux 主机控制 Jabra 710 USB 扬声器音量

linux - 如何在 Linux 下使用 SWD 对 ARM MCU 进行编程?

linux - 在 Amazon Linux 上使用堆栈静态链接 Haskell 程序,以在 AWS Lambda 上使用

Javassist : How do I call a nondefault constructor on a dynamically generated class?

arrays - 为什么汇编代码在加总和之前将值从 %edx 复制到 %rcx?