如何使用内联汇编调用存储在数组中的十六进制数据?

How do I call hex data stored in an array with inline assembly?

我有一个正在处理的 OS 项目,我正在尝试使用内联汇编调用从磁盘读取的数据。

我已经尝试使用内联汇编读取代码并使用汇编调用指令执行它。

void driveLoop() {
    uint16_t sectors = 31;
    uint16_t sector = 0;
    uint16_t basesector = 40000;
    uint32_t i = 40031;
    uint16_t code[sectors][256];
    int x = 0;
    while(x==0) {
        read(i);
        for (int p=0; p < 256; p++) {
            if (readOut[p] == 0) {
            } else {
                x = 1;
                //kprint_int(i);
            }
        }
        i++;
    }
    kprint("Found sector!\n");
    kprint("Loading OS into memory...\n");
    for (sector=0; sector<sectors; sector++) {
        read(basesector+sector);
        for (int p=0; p<256; p++) {
            code[sector][p] = readOut[p];
        }
    }
    kprint("Done loading.\n");
    kprint("Attempting to call...\n");
    asm volatile("call (%0)" : : "r" (&code));

当调用内联程序集时,我希望它 运行 我从 "disk" 读取的扇区代码(这是在 VM 中,因为它是一个爱好 OS ).它所做的只是挂起。

我可能不太了解变量、数组和程序集的工作原理,所以如果你能帮我补充一下,那就太好了。

编辑:我从磁盘读取的数据是添加的二进制文件 使用

到磁盘映像文件
cat kernel.bin >> disk.img

并且 kernel.bin 是用

编译的
i686-elf-ld -o kernel.bin -Ttext 0x4C4B40 *insert .o files here* --oformat binary

What it does instead is it just hangs.

运行 你在 BOCHS 中的 OS 这样你就可以使用 BOCHS 的 built-in 调试器来查看卡住的确切位置。

能够调试锁定,包括禁用中断,可能非常有用...


asm volatile("call (%0)" : : "r" (&code)); 不安全,因为缺少 clobbers。

但更糟糕的是,它会从数组的前 4 个字节加载一个新的 EIP 值,而不是将 EIP 设置为该地址。 (除非你加载的数据是一个指针数组,而不是实际的机器代码?)

括号里有%0,所以是寻址方式。 assembler 将警告您没有 * 的间接调用,但 assemble 它会像 call *(%eax) 一样,EAX = code[0][0] 的地址。您实际上想要 call *%eax 或编译器选择的任何寄存器,register-indirect 而不是 memory-indirect.

&codecode 都只是指向数组开头的指针; &code 不会创建存储另一个地址的匿名指针对象。 &code 取整个数组的地址。 code 在此上下文中 "decays" 指向第一个对象的指针。


https://gcc.gnu.org/wiki/DontUseInlineAsm(为此)。

您可以通过将指针转换为函数指针来让编译器发出 call 指令。

   __builtin___clear_cache(&code[0][0], &code[30][255]);   // don't optimize away stores into the buffer
   void (*fptr)(void) =  (void*)code;                     // casting to void* instead of the actual target type is simpler

   fptr();

对于 32 位 x86,这将编译(启用优化)为类似 lea 16(%esp), %eax / call *%eax 的内容,因为您的 code[][] 缓冲区是堆栈上的数组。

或者让它发出一个 jmp,在 void 函数的末尾执行,或者在 non-void 函数中执行 return funcptr();,所以编译器可以将 call/ret 优化为 jmp 尾调用。

如果没有return,可以用__attribute__((noreturn))声明。


确保内存页/段是可执行的。 (你的 uint16_t code[]; 是本地的,所以 gcc 会在堆栈上分配它。这可能不是你想要的。大小是一个 compile-time 常量,所以你可以把它设为 static,但是如果您对其他兄弟函数(不是父函数或子函数)中的其他数组执行此操作,那么您将失去为不同数组重用大量堆栈内存的能力。)

这比你不安全的内联汇编要好得多。 (您忘记了 "memory" 破坏,因此没有任何信息告诉编译器您的 asm 实际上读取了 pointed-to 内存)。另外,您忘记声明任何寄存器破坏;如果它 returns,大概你加载的代码块会破坏一些寄存器,除非它被写入 save/restore everything.

在 GNU C 中你需要使用 __builtin__clear_cache when casting a data pointer to a function pointer. On x86 it doesn't actually clear any cache, it's telling the compiler that the stores to that memory are not dead because it's going to be read by execution. See

没有它,gcc 可以优化复制到 uint16_t code[sectors][256]; 的操作,因为它看起来像一个死存储。 (就像您当前的内联汇编一样,它只要求寄存器中的指针。)

作为奖励,您的 OS 的这一部分可以移植到其他体系结构,包括像 ARM 这样没有连贯指令缓存的体系结构,其中内置扩展为实际指令。 (在 x86 上它纯粹影响优化器)。


read(basesector+sector);

您的 read 函数采用目标指针进行读入可能是个好主意,这样您就不需要通过 readOut 缓冲区反弹数据。

此外,我不明白您为什么要将代码声明为二维数组;扇区是您如何处理磁盘的产物 I/O,与加载后使用代码无关。 sector-at-a-time 应该只在加载数据的循环代码中,在程序的其他部分不可见。

char code[sectors * 512];就好了