Gcc内联汇编:什么';输入操作数中的动态分配寄存器'r'有什么问题?
当我测试GCC内联程序集时,我使用Gcc内联汇编:什么';输入操作数中的动态分配寄存器'r'有什么问题?,gcc,x86,inline-assembly,osdev,bochs,Gcc,X86,Inline Assembly,Osdev,Bochs,当我测试GCC内联程序集时,我使用test函数在BOCHS模拟器的屏幕上显示一个字符。此代码以32位保护模式运行。代码如下: test() { char ch = 'B'; __asm__ ("mov $0x10, %%ax\n\t" "mov %%ax, %%es\n\t" "movl $0xb8000, %%ebx\n\t" "mov $0x04, %%ah\n\t"
test
函数在BOCHS模拟器的屏幕上显示一个字符。此代码以32位保护模式运行。代码如下:
test() {
char ch = 'B';
__asm__ ("mov $0x10, %%ax\n\t"
"mov %%ax, %%es\n\t"
"movl $0xb8000, %%ebx\n\t"
"mov $0x04, %%ah\n\t"
"mov %0, %%al\n\t"
"mov %%ax, %%es: ((80 * 3 + 40) * 2)(%%ebx)\n\t"
::"r"(ch):);
}
我得到的结果是:
屏幕上的红色字符未正确显示B
。但是,当我将输入寄存器r
更改为c
时,如下所示::“c”(ch):)代码>,这是上述代码的最后一行,字符“B”正常显示:
有什么区别?电脑进入保护模式后,我直接通过数据段访问视频存储器
我跟踪了汇编代码,发现当选择r
寄存器且ax
的值为0x0010
时,代码已被汇编到mov al,al
,因此al
为0x10
。结果应该是这样的,但它为什么选择al
寄存器呢。它不是应该选择以前没用过的寄存器吗?当我添加clobbers
列表时,我已经解决了这个问题。就像@MichaelPetch评论的那样,您可以使用32位地址从C访问任何您想要的内存。asm gcc将假设一个平坦的内存空间,并假设它可以将esp
复制到edi
并使用rep stos
将一些堆栈内存归零,例如(这要求%es
与%ss
具有相同的基础)
我想最好的解决方案不是使用任何内联asm,而是使用一个全局常量作为指向char
的指针。e、 g
// pointer is constant, but points to non-const memory
uint16_t *const vga_base = (uint16_t*)0xb8000; // + whatever was in your segment
// offsets are scaled by 2. Do some casting if you want the address math to treat offsets as byte offsets
void store_in_flat_memory(unsigned char c, uint32_t offset) {
vga_base[offset] = 0x0400U | c; // it matters that c is unsigned, so it zero-extends instead of sign-extending
}
movzbl 4(%esp), %eax # c, c
movl 8(%esp), %edx # offset, offset
orb $4, %ah #, tmp95 # Super-weird, wtf gcc. We get this even for -mtune=core2, where it causes a partial-register stall
movw %ax, 753664(%edx,%edx) # tmp95, *_3 # the addressing mode scales the offset by two (sizeof(uint16_t)), by using it as base and index
ret
从锁紧螺栓上的gcc6.1(下面的链接),带有-O3-m32
如果没有const
,像vga_base[10]=0x4这样的代码,我对此不太了解,但似乎很友好。我以前读过手册,可能不太仔细,但我现在找不到结果。你是在realmode还是protected mode?你可能会遇到的一个潜在问题是,你修改了汇编程序模板中的许多寄存器,但没有将它们列为输出或缓冲。我很好奇一件事。我知道您使用汇编程序的全部原因是重写默认段。我很好奇,你确定你需要吗?我无法说出代码的上下文(看起来它应该在内核本身中)。通常最简单的方法是在内核加载时将DS=ES=SS(如果您愿意,甚至是FS和GS)设置为所有相同的描述符(在本例中为0x10)(最简单的方法是为所有4gb设置一个平面描述符)。也许你不能这么做是有原因的,但如果你可以这样做,你就不需要重写ES(或将其作为mov
指令的一部分),他的代码必须以实模式运行,因为他将ES
设置为0xb800
写入屏幕,对吗?因此没有“平面”寻址(是的,我知道非真实模式,但不是因为他改变了es
)@doug65536:在评论中,OP说es=DS=SS
,他正在设置它“以防它被改变”/脸掌。但是没有,OP的代码设置%es=0x10
,并将0xb800
放入寄存器,并将其偏移到。所以它在做我的代码所做的:%es:0xb800+偏移量
。是的,没错,es
得到0x10
。。。抱歉,忘记我的评论:)我对评论感到困惑。我的代码中设置为0x10
的es
处于保护模式,选择器0x10
基址是0x00000000
,因此我将ebx
设置为0xb8000
以访问视频内存,但这并不重要。问题中的代码看起来很像我在真实模式引导时所做的一些启动代码,我的大脑点击进入真实模式。但是,该代码集es
。
void store_in_special_segment(unsigned char c, uint32_t offset) {
char *base = (char*)0xb8000; // sizeof(char) = 1, so address math isn't scaled by anything
// let the compiler do the address math at compile time, instead of forcing one 32bit constant into a register, and another into a disp32
char *dst = base+offset; // not a real address, because it's relative to a special segment. We're using a C pointer so gcc can take advantage of whatever addressing mode it wants.
uint16_t val = (uint32_t)c | 0x0400U; // it matters that c is unsigned, so it zero-extends
asm volatile ("movw %[val], %%fs: %[dest]\n"
:
: [val] "ri" (val), // register or immediate
[dest] "m" (*dst)
: "memory" // we write to something that isn't an output operand
);
}
movzbl %dil, %edi # dil is the low 8 of %edi (AMD64-only, but 32bit code prob. wouldn't put a char there in the first place)
orw $1024, %di #, val # gcc causes an LCP stall, even with -mtune=haswell, and with gcc 6.1
movw %di, %fs: 753664(%esi) # val, *dst_2
void test_const_args(void) {
uint32_t offset = (80 * 3 + 40) * 2;
store_in_special_segment('B', offset);
}
movw $1090, %fs: 754224 #, MEM[(char *)754224B]
void test_const_offset(char ch) {
uint32_t offset = (80 * 3 + 40) * 2;
store_in_special_segment(ch, offset);
}
movzbl %dil, %edi # ch, ch
orw $1024, %di #, val
movw %di, %fs: 754224 # val, MEM[(char *)754224B]
void test_const_char(uint32_t offset) {
store_in_special_segment('B', offset);
}
movw $1090, %fs: 753664(%edi) #, *dst_4