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

时间:2019-05-20 01:37:02

标签: c x86 operating-system inline-assembly osdev

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

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

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));

当调用内联程序集时,我希望它从我从“磁盘”中读取的扇区中运行代码(这是在VM中,因为它是一个业余OS)。相反,它只是挂了。

我可能不太了解变量,数组和汇编的工作原理,所以如果可以的话,那会很好。

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

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

和kernel.bin一起编译

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

1 个答案:

答案 0 :(得分:1)

  

它只是挂了。

在BOCHS中运行您的操作系统,以便您可以使用BOCHS的内置调试器来确切地了解其卡住位置。

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


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

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

括号中有%0,因此它是一种寻址模式。汇编器会警告您有关不使用*的间接调用,但是它将像call *(%eax)那样进行汇编,其中EAX = code[0][0]的地址。实际上,您实际上希望使用call *%eax或编译器选择的任何寄存器,间接寄存器而不是间接存储器。

&codecode都只是指向数组开头的指针。 &code不会创建一个匿名指针对象来存储另一个地址的地址。 &code将数组的地址作为一个整体。 code在这种情况下“衰减”到第一个对象的指针。


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函数的末尾执行,或在非void函数的return funcptr();末尾执行,以便编译器可以优化调用/ ret进入jmp尾注。

如果未返回,则可以使用__attribute__((noreturn))进行声明。


确保内存页/段可以执行。 (您的uint16_t code[];是本地变量,因此gcc会将其分配到堆栈上。这可能不是您想要的。大小是一个编译时常量,因此可以将其设为static,但如果对其他同级函数中的其他数组(不是父级或子级)执行此操作,那么您就失去了为不同数组重用大量堆栈内存的能力。)

这比不安全的嵌入式汇编好得多。 (您忘记了"memory"破坏者,因此什么也没有告诉编译器您的asm实际上正在读取指向的内存)。另外,您忘记声明任何寄存器复制项;假定您加载的代码块返回时会破坏一些寄存器,除非它被编写为保存/恢复所有内容。

在GNU C中,在将数据指针转换为函数指针时确实需要使用__builtin__clear_cache 。在x86上,它实际上并没有清除任何高速缓存,而是告诉编译器该内存的存储没有死,因为它将由执行读取。参见How does __builtin___clear_cache work?

否则,gcc可以优化复制到uint16_t code[sectors][256];的过程,因为它看起来像是无效存储。 (就像您当前的内联汇编仅在寄存器中要求指针一样。)

作为奖励,您的操作系统的这一部分可移植到其他体系结构中,包括像ARM这样的不带一致性指令高速缓存的体系结构,其中的内置扩展成实际的指令。 (在x86上,它纯粹影响优化器。)


  

read(basesector+sector);

您的read函数最好将目标指针读入,因此您不需要通过readOut缓冲区来回跳数据。

此外,我不明白为什么要将代码声明为2D数组;为什么?扇区是有关如何进行磁盘I / O的工件,与加载代码后使用代码无关。一次扇区的事务只能出现在加载数据的循环的代码中,而在程序的其他部分则不可见。

char code[sectors * 512];会很好。