Gcc内联汇编:什么';输入操作数中的动态分配寄存器'r'有什么问题?

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"

当我测试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" 
                "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