我有一个正在处理的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
答案 0 :(得分:1)
它只是挂了。
在BOCHS中运行您的操作系统,以便您可以使用BOCHS的内置调试器来确切地了解其卡住位置。
能够调试锁定(包括禁用中断)可能非常有用...
asm volatile("call (%0)" : : "r" (&code));
不安全,因为缺少Clobbers。
但更糟糕的是,它将从数组的前4个字节中加载新的EIP值,而不是将EIP设置为该地址。 (除非要加载的数据是指针数组,而不是实际的机器代码?)
括号中有%0
,因此它是一种寻址模式。汇编器会警告您有关不使用*
的间接调用,但是它将像call *(%eax)
那样进行汇编,其中EAX = code[0][0]
的地址。实际上,您实际上希望使用call *%eax
或编译器选择的任何寄存器,间接寄存器而不是间接存储器。
&code
和code
都只是指向数组开头的指针。 &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];
会很好。