如何从内联汇编中的两个寄存器中检索值的高位和低位部分?
How do I retrieve high and low-order parts of a value from two registers in inline assembly?
我目前正在开发一款小游戏,它可以 运行 从硬盘驱动器的引导扇区中获取,只是为了找点乐子。这意味着我的程序 运行s 在 16 位实模式下,并且我的编译器标志设置为发出纯 i386 代码。我正在用 C++ 编写游戏,但我确实需要大量内联汇编来通过中断调用与 BIOS 对话。其中一些调用 return 一个 32 位整数,但存储在两个 16 位寄存器中。目前我正在执行以下操作以将我的电话号码从集会中删除:
auto getTicks = [](){
uint16_t ticksL{ 0 }, ticksH{ 0 };
asm volatile("int [=10=]x1a" : "=c"(ticksH), "=d"(ticksL) : "a"(0x0));
return static_cast<uint32_t>( (ticksH << 16) | ticksL );
};
这是我用来调用 this interrupt function 的 lambda 函数,return 是一个滴答计数。我知道有更好的方法来获取时间数据,而且我还没有实施检查 AL
以查看午夜是否已经过去,但这是另一个话题。
如您所见,我必须使用两个 16 位值,分别获取寄存器值,然后按照您在 return 语句中看到的方式将它们组合成一个 32 位数字。
有什么方法可以立即将数据检索到我的代码中的单个 32 位数字中,从而避免移位和按位或?我知道我正在访问的那些 16 位寄存器实际上只是 32 位寄存器的高 16 位和低 16 位,但我不知道如何访问整个原始 32 位寄存器。
您正在寻找 'A' 约束,它指的是 dx:ax 寄存器对作为双宽度值。您可以看到 x86 in the gcc documentation 的完整定义约束集。不幸的是,对于任何其他寄存器对都没有限制,因此您必须将它们作为两个值获取,然后像您描述的那样使用 shift 和 or 重新组合它们。
I know that those 16-bit registers I'm accessing are really just the higher and lower 16-bits of a 32-bit register in reality, but I have no idea how to access the original 32-bit register as a whole.
正如 Jester 已经指出的那样,这些实际上是 2 个独立的寄存器,因此无法检索 "the original 32-bit register."
另一点:该中断修改了 ax 寄存器(返回 'past midnight' 标志),但是您的 asm 不会通知 gcc 您正在更改 ax。我可以这样建议吗:
asm volatile("int [=10=]x1a" : "=c"(ticksH), "=d"(ticksL), "=a"(midnight) : "a"(0x0));
请注意 midnight
也是 uint16_t。
正如其他答案所建议的那样,您不能将 DX 和 CX 直接加载到 32 位寄存器中。你必须按照你的建议将它们组合起来。
在这种情况下,还有另一种选择。而不是在低位内存中使用 INT 1Ah/AH=0h you can read the BIOS Data Area (BDA) 作为 32 位 DWORD 值并将其加载到 32 位寄存器中。这在 i386 处理器的实模式下是允许的。两个感兴趣的内存地址:
40:6C dword Daily timer counter, equal to zero at midnight;
incremented by INT 8; read/set by INT 1A
40:70 byte Clock rollover flag, set when 40:6C exceeds 24hrs
这两个内存地址是segment:offset格式,但相当于物理地址0x0046C和0x00470。
你所要做的就是暂时将 DS 寄存器设置为 0(保存之前的值),使用 CLI[=34 关闭中断=] 使用 C/C++ 指针从较低内存中检索值,使用 STI 重新启用中断并将 DS 恢复到之前的值保存的价值。与使用 INT 1Ah/AH=0h 相比,这当然会增加引导扇区的开销,但可以让您直接访问 BIOS 代表您 reading/writing 的内存地址.
注意:如果DS设置为零已经不需要save/set/restore了。由于我们没有看到在调用 C++ 代码之前设置环境的代码,所以我不知道您的默认段值是什么。如果您不需要同时检索翻转值和计时器值,而只想单独获取它们,则可以消除 CLI/STI。
我目前正在开发一款小游戏,它可以 运行 从硬盘驱动器的引导扇区中获取,只是为了找点乐子。这意味着我的程序 运行s 在 16 位实模式下,并且我的编译器标志设置为发出纯 i386 代码。我正在用 C++ 编写游戏,但我确实需要大量内联汇编来通过中断调用与 BIOS 对话。其中一些调用 return 一个 32 位整数,但存储在两个 16 位寄存器中。目前我正在执行以下操作以将我的电话号码从集会中删除:
auto getTicks = [](){
uint16_t ticksL{ 0 }, ticksH{ 0 };
asm volatile("int [=10=]x1a" : "=c"(ticksH), "=d"(ticksL) : "a"(0x0));
return static_cast<uint32_t>( (ticksH << 16) | ticksL );
};
这是我用来调用 this interrupt function 的 lambda 函数,return 是一个滴答计数。我知道有更好的方法来获取时间数据,而且我还没有实施检查 AL
以查看午夜是否已经过去,但这是另一个话题。
如您所见,我必须使用两个 16 位值,分别获取寄存器值,然后按照您在 return 语句中看到的方式将它们组合成一个 32 位数字。
有什么方法可以立即将数据检索到我的代码中的单个 32 位数字中,从而避免移位和按位或?我知道我正在访问的那些 16 位寄存器实际上只是 32 位寄存器的高 16 位和低 16 位,但我不知道如何访问整个原始 32 位寄存器。
您正在寻找 'A' 约束,它指的是 dx:ax 寄存器对作为双宽度值。您可以看到 x86 in the gcc documentation 的完整定义约束集。不幸的是,对于任何其他寄存器对都没有限制,因此您必须将它们作为两个值获取,然后像您描述的那样使用 shift 和 or 重新组合它们。
I know that those 16-bit registers I'm accessing are really just the higher and lower 16-bits of a 32-bit register in reality, but I have no idea how to access the original 32-bit register as a whole.
正如 Jester 已经指出的那样,这些实际上是 2 个独立的寄存器,因此无法检索 "the original 32-bit register."
另一点:该中断修改了 ax 寄存器(返回 'past midnight' 标志),但是您的 asm 不会通知 gcc 您正在更改 ax。我可以这样建议吗:
asm volatile("int [=10=]x1a" : "=c"(ticksH), "=d"(ticksL), "=a"(midnight) : "a"(0x0));
请注意 midnight
也是 uint16_t。
正如其他答案所建议的那样,您不能将 DX 和 CX 直接加载到 32 位寄存器中。你必须按照你的建议将它们组合起来。
在这种情况下,还有另一种选择。而不是在低位内存中使用 INT 1Ah/AH=0h you can read the BIOS Data Area (BDA) 作为 32 位 DWORD 值并将其加载到 32 位寄存器中。这在 i386 处理器的实模式下是允许的。两个感兴趣的内存地址:
40:6C dword Daily timer counter, equal to zero at midnight; incremented by INT 8; read/set by INT 1A 40:70 byte Clock rollover flag, set when 40:6C exceeds 24hrs
这两个内存地址是segment:offset格式,但相当于物理地址0x0046C和0x00470。
你所要做的就是暂时将 DS 寄存器设置为 0(保存之前的值),使用 CLI[=34 关闭中断=] 使用 C/C++ 指针从较低内存中检索值,使用 STI 重新启用中断并将 DS 恢复到之前的值保存的价值。与使用 INT 1Ah/AH=0h 相比,这当然会增加引导扇区的开销,但可以让您直接访问 BIOS 代表您 reading/writing 的内存地址.
注意:如果DS设置为零已经不需要save/set/restore了。由于我们没有看到在调用 C++ 代码之前设置环境的代码,所以我不知道您的默认段值是什么。如果您不需要同时检索翻转值和计时器值,而只想单独获取它们,则可以消除 CLI/STI。