问题描述
我有一个正在处理的OS项目,并且试图通过内联汇编调用从C中的磁盘读取的数据.
I have an OS project that I am working on and I am trying to call data that I have read from the disk in C with inline assembly.
我已经尝试使用内联汇编读取代码并使用汇编调用指令执行它.
I have already tried reading the code and executing it with the assembly call instruction, using inline assembly.
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中,因为它是一个业余操作系统).相反,它只是挂了.
When the inline assembly is called I expect it to run the code from the sectors I read from the "disk" (this is in a VM, because its a hobby OS). What it does instead is it just hangs.
我可能不太了解变量,数组和汇编的工作原理,所以如果您能填写我的话,那会很好.
I probably don't much understand how variables, arrays, and assembly work, so if you could fill me in, that would be nice.
我从磁盘读取的数据是已添加的二进制文件使用
The data I am reading from the disk is a binary file that was addedto the disk image file with
cat kernel.bin >> disk.img
和kernel.bin一起编译
and the kernel.bin is compiled with
i686-elf-ld -o kernel.bin -Ttext 0x4C4B40 *insert .o files here* --oformat binary
推荐答案
在BOCHS中运行您的操作系统,以便您可以使用BOCHS的内置调试器来确切地了解其卡住的位置.
能够调试锁定(包括禁用中断)可能非常有用...
Being able to debug lockups, including with interrupts disabled, is probably very useful...
asm volatile("call(%0)"::"r"(& code));
由于缺少Clobbers是不安全的.
asm volatile("call (%0)" : : "r" (&code));
is unsafe because of missing clobbers.
但更糟糕的是,它将从数组的前4个字节中加载新的EIP值,而不是将EIP设置为该地址.(除非要加载的数据是指针数组,而不是实际的机器代码?)
But even worse than that it will load a new EIP value from the first 4 bytes of the array, instead of setting EIP to that address. (Unless the data you're loading is an array of pointers, not actual machine code?)
您在括号中有%0
,因此它是一种寻址模式.汇编器会警告您有关不带 *
的间接调用,但是它将像 call *(%eax)
那样进行汇编,其中EAX = code [0]的地址] [0]
.实际上,您实际上想要的是 call *%eax
或编译器选择的任何寄存器,间接寄存器而不是内存间接
You have the %0
in parentheses, so it's an addressing mode. The assembler will warn you about an indirect call without *
, but will assemble it like call *(%eax)
, with EAX = the address of code[0][0]
. You actually want a call *%eax
or whatever register the compiler chooses, register-indirect not memory-indirect.
& code
和 code
都只是指向数组开头的指针.& code
不会创建存储另一个地址的匿名指针对象.& code
将数组的地址作为一个整体.在这种情况下, code
会衰减"到第一个对象的指针.
&code
and code
are both just a pointer to the start of the array; &code
doesn't create an anonymous pointer object storing the address of another address. &code
takes the address of the array as a whole. code
in this context "decays" to a pointer to the first object.
https://gcc.gnu.org/wiki/DontUseInlineAsm (为此).
https://gcc.gnu.org/wiki/DontUseInlineAsm (for this).
通过将指针转换为函数指针,可以使编译器发出 call
指令.
You can get the compiler to emit a call
instruction by casting the pointer to a function pointer.
__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 [] []
缓冲区是堆栈上的一个数组.
That will compile (with optimization enabled) to something like lea 16(%esp), %eax
/ call *%eax
, for 32-bit x86, because your code[][]
buffer is an array on the stack.
或者要使其发出 jmp
,请在 void
函数的末尾执行,或在 return funcptr();
非空函数,因此编译器可以优化对 jmp
尾调用的调用/返回.
Or to have it emit a jmp
instead, do it at the end of a void
function, or return funcptr();
in a non-void function, so the compiler can optimize the call/ret into a jmp
tailcall.
如果不返回,则可以使用 __ attribute __((noreturn))
声明.
If it doesn't return, you can declare it with __attribute__((noreturn))
.
确保内存页/段可以执行.(您的 uint16_t code [];
是本地的,因此gcc会将其分配到堆栈上.这可能不是您想要的.大小是编译时常量,因此您可以将其设置为 static
,但是如果对其他同级函数(而不是父级或子级)中的其他阵列执行此操作,则会失去为不同阵列重用大量堆栈内存的能力.)
Make sure the memory page / segment is executable. (Your uint16_t code[];
is a local, so gcc will allocate it on the stack. This might not be what you want. The size is a compile-time constant so you could make it static
, but if you do that for other arrays in other sibling functions (not parent or child), then you lose out on the ability to reuse a big chunk of stack memory for different arrays.)
这比不安全的内联汇编要好得多.(您忘记了内存"
破坏器,因此没有任何内容告诉编译器您的asm实际上正在读取指向的内存).另外,您忘记声明任何寄存器复制器;假定您加载的代码块返回时将破坏某些寄存器,除非编写该代码块是为了保存/恢复所有内容.
This is much better than your unsafe inline asm. (You forgot a "memory"
clobber, so nothing tells the compiler that your asm actually reads the pointed-to memory). Also, you forgot to declare any register clobbers; presumably the block of code you loaded will have clobbered some registers if it returns, unless it's written to save/restore everything.
在GNU C中,您确实需要使用 __ builtin__clear_cache
将数据指针转换为函数指针时.在x86上,它实际上并没有清除任何缓存,而是告诉编译器该内存的存储没有死,因为它将由执行读取.请参见 __builtin___clear_cache如何工作?
In GNU C you do need to use __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 How does __builtin___clear_cache work?
否则,gcc可以优化复制到 uint16_t code [sectors] [256];
的过程,因为它看起来像是无效存储.(就像您当前的内联汇编仅在寄存器中要求指针一样.)
Without that, gcc could optimize away the copying into uint16_t code[sectors][256];
because it looks like a dead store. (Just like with your current inline asm which only asks for the pointer in a register.)
另外,您的操作系统的这一部分可移植到其他体系结构中,包括像ARM这样的不带一致性指令高速缓存的体系结构,其内置扩展为实际的指令.(在x86上,它纯粹影响优化器.)
As a bonus, this part of your OS becomes portable to other architectures, including ones like ARM without coherent instruction caches where that builtin expands to a actual instructions. (On x86 it purely affects the optimizer).
对于您的 read
函数而言,采用目标指针进行读入可能是一个好主意,因此您无需通过 readOut
缓冲区反弹数据
It would probably be a good idea for your read
function to take a destination pointer to read into, so you don't need to bounce data through your readOut
buffer.
此外,我不明白为什么要将代码声明为2D数组;为什么?扇区是有关如何进行磁盘I/O的工件,与加载代码后使用代码无关.一次扇区的事务只能出现在加载数据的循环的代码中,而在程序的其他部分则不可见.
Also, I don't see why you'd want to declare your code as a 2D array; sectors are an artifact of how you're doing your disk I/O, not relevant to using the code after it's loaded. The sector-at-a-time thing should only be in the code for the loop that loads the data, not visible in other parts of your program.
字符代码[sectors * 512];
会很好.
这篇关于如何通过内联汇编调用存储在数组中的十六进制数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!