X64处理器架构

2023-11-10

 

X64处理器架构(翻译的windbg帮助文档)

X64处理器架构

X64 架构是一个向后兼容的扩展的 x86 。提供了和 x86 相同的 32 位模式和一个新的 64 位模式。
术语“ x64 ”包括 AMD 64 Intel64 ,他们的指令集基本是相同的。
寄存器(Registers
X64x868个通用寄存器扩展为64位,并且增加8个新的64位寄存器。64位寄存器命名以“r”开始,例如:eax扩展为64位就是rax8个新的64位寄存器命名为r8r15
每个寄存器的低 32 位, 16 位, 8 位可以作为操作数直接寻址,这包括向 esi 这样的寄存器,以前他的低 8 位不可以直接寻址。下表说明了 64 位寄存器的地位部分在汇编语言中的命名。

64-bit register

Lower 32 bits

Lower 16 bits

Lower 8 bits

rax

eax

ax

al

rbx

ebx

bx

bl

rcx

ecx

cx

cl

rdx

edx

dx

dl

rsi

esi

si

sil

rdi

edi

di

dil

rbp

ebp

bp

bpl

rsp

esp

sp

spl

r8

r8d

r8w

r8b

r9

r9d

r9w

r9b

r10

r10d

r10w

r10b

r11

r11d

r11w

r11b

r12

r12d

r12w

r12b

r13

r13d

r13w

r13b

r14

r14d

r14w

r14b

r15

r15d

r15w

r15b

对一个 32 位寄存器操作会自动用零扩展填充整个 64 位寄存器。对 8 位和 16 位寄存器的操作不会零扩展填充高位(这是和 x86 兼容的)。

ax
bx cx dx 的高 8 ah bh ch dh 仍就是可以寻址的,但是不能用在所有类型的操作数。

指令指针寄存器 eip flags 也被扩展到 64 位(分别为 rip rflags )。

X64
处理器也提供几个浮点寄存器:


·8个80位的x87寄存器

·8个64位的MMX寄存器

·以前的8个128位SSE寄存器增加到16个
调用约定(Calling Conventions

跟x86不同,在x64下c/c++编译器仅支持一种调用约定,这种调用约定利用了在x64下可用寄存器的增加。

·前四个整型值或指针参数传给寄存器rcx,rdx,r8,和r9。调用函数在堆栈上保留空间为这些参数。

·前四个浮点参数传给前四个SSE寄存器xmm0-xmm3.

·调用函数在堆栈上保留空间为传递给寄存器的参数。被调用函数利用这些空间将寄存器的内容存入堆栈。

·任何其他参数存入堆栈

·一个整型或指针返回值存在rax寄存器中,如果返回值是浮点则返回在xmm0中

·rax,rcx,rdx,r8-r11是要变化的

·rbx, rbp, rdi, rsi, r12-r15不变

这个调用约定跟c++是非常相似的:this指针作为第一个隐含的参数被传递,后面三个参数传递给寄存器,剩下的存入堆栈。
寻址方式( Addressing Modes
64位模式下的寻址方式类似于x86但是不是完全相同。
·指令涉及到64位寄存器会自动执行64位精度。(例如mov rax,[rbx]是将rbx所指向的地址开始的8字节存入rax)
·一个特别的指令mov的立即数常量或常量地址已经增加为64位,对于其他的指令立即数常量或常量指针仍就是32位。
·x64提供了一个新的rip相关的寻址模式。如果指令涉及到常量地址以rip为偏移。例如mov rax,[addr]操作将地址addr+rip指向地址开始的8字节数存入rax。
Jmp ,call,push和pop指令涉及到的指令指针和堆栈指针都为64位在x64中。

x64 指令集

大多数x86指令在x64的64位模式下是有效的。在64位模式下一些很少用到的指令不再支持。例如:

·BCD码算术指令:AAA,AAD,AAM,AAS,DAA,DAS

·BOUND

·PUSHAD

POPAD

·大多数的操作要处理段寄存器,例如PUSH DS 和 POP DS。(对FS和 GS段寄存器的操作仍然有效)


X64
指令集包括最近增加的x86指令例如SSE2,程序中可以自由的使用这些指令。

数据传送(Data Transfer

X64提供新的MOV指令的变量来处理64位立即数常量或内存地址。

MOV

r,#n

r = #n

MOV

rax, m

传送64位地址处的内容到 rax.

MOV

m, rax

传送 rax
的内容到64位地址处

X64也提供一个新的指令符号扩展32位到64位

MOVSXD

r1, r/m

传送 DWORD 符号扩展到 QWORD.

一般MOV操作32位子寄存器自动零扩展到64位,因此没有MOVZXD指令。

两个SSE指令可以用来传送128位值(例如GUIDs)从内存到xmmn 寄存器或相反。

MOVDQA

r1/m, r2/m

  传送128位对齐值到xmmn 寄存器,或相反

MOVDQU

r1/m, r2/m

传送128位值(不是必须对齐)到寄存器或相反

数据转换(Data Conversion)

CDQE

转换 dword (eax) 为 qword (rax).

CQO

转换 qword (rax) 为 oword (rdx:rax).

字符串操作(String Manipulation)

MOVSQ

将rsi指向的字符串传送到rdi指向地址

CMPSQ

比较rsi和rdi所指向地址的字符串

SCASQ

扫描rdi指向的地址的qword并与rax比较

LODSQ

将rsi指向的地址的qword传入rax

STOSQ

将rax的值传入rdi指向的地址

x64反汇编

下面一个非常简单的函数来说明x64调用约定。
int Simple(int i, int j)
{
    return i*5 + j + 3;
}

编译后的代码是这样:

01001080 lea     eax,[rdx+rcx*4]        ; eax = rdx+rcx*4
01001083 lea     eax,[rcx+rax+0x3]      ; eax = rcx+rax+3
01001087 ret
ij参数被传递给ecxedx寄存器,由于这仅有两个参数这个函数根本没用堆栈。

这段生成的代码有三个地方值得注意,其中有一个事x64特有的:
1.
lea指令被用来执行一系列的简单算术操作,第一个将j+i*4存入eax,第二个操作加上i+3存入结果中,最后为j+i*5+3
2.
许多操作例如加和乘,可以处理中用扩展精度,然后在舍入到正确精度。在这个例子中代码用的是64位加和乘操作。我们可以安全的缩短结果到32位。
3.
x64中,任何输出到32位寄存器的操作会自动零扩展到64位,在这个例子中,输出到eax中有效的缩短到32位。
返回值被传送到rax寄存器,在这个例子中,结果已经在rax寄存器中,因此函数直接返回。
下面我们考虑一个更复杂的函数来说明典型的x64反汇编:
HRESULT Meaningless(IDispatch *pdisp, DISPID dispid, BOOL fUnique, LPCWSTR pszExe)
{
    IQueryAssociations *pqa;
    HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
    if (SUCCEEDED(hr)) {
        hr = pqa->Init(ASSOCF_INIT_BYEXENAME, pszExe, NULL, NULL);
        if (SUCCEEDED(hr)) {
            WCHAR wszName[MAX_PATH];
            DWORD cchName = MAX_PATH;
            hr = pqa->GetString(0, ASSOCSTR_FRIENDLYAPPNAME, NULL, wszName, &cchName);
            if (SUCCEEDED(hr)) {
                VARIANTARG rgvarg[2] = { 0 };
                V_VT(&rgvarg[0]) = VT_BSTR;
                V_BSTR(&rgvarg[0]) = SysAllocString(wszName);
                if (V_BSTR(&rgvarg[0])) {
                    DISPPARAMS dp;
                    LONG lUnique = InterlockedIncrement(&lCounter);
                    V_VT(&rgvarg[1]) = VT_I4;
                    V_I4(&rgvarg[1]) = fUnique ? lUnique : 0;
                    dp.rgvarg = rgvarg;
                    dp.cArgs = 2;
                    dp.rgdispidNamedArgs = NULL;
                    dp.cNamedArgs = 0;
                    hr = pdisp->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dp, NULL, NULL, NULL);
                    VariantClear(&rgvarg[0]);
                    VariantClear(&rgvarg[1]);
                } else {
                    hr = E_OUTOFMEMORY;
                }
            }
        }
        pqa->Release();
    }
    return hr;
}
我们将要进入这个函数并且对每行进行反汇编。

当进入的时候这个函数的参数被存储为下面这样:
  • rcx = pdisp.
  • rdx = dispid.
  • r8 = fUnique.
  • r9 = pszExe.

前四个参数被传入寄存器中,由于这个函数仅有四个参数,没有一个被存入堆栈中。

下面开始汇编代码:

Meaningless:
010010e0 push    rbx                    ; save
010010e1 push    rsi                    ; save
010010e2 push    rdi                    ; save
010010e3 push    r12d                   ; save
010010e5 push    r13d                   ; save
010010e7 push    r14d                   ; save
010010e9 push    r15d                   ; save
010010eb sub     rsp,0x2c0              ; reserve stack
010010f2 mov     rbx,r9                 ; rbx = pszExe
010010f5 mov     r12d,r8d               ; r12 = fUnique (zero-extend)
010010f8 mov     r13d,edx               ; r13 = dispid  (zero-extend)
010010fb mov     rsi,rcx                ; rsi = pdisp

这个函数开始保存不可变的寄存器,然后保留堆栈空间为局部变量,再保存参数到不可变寄存器。注意中间两个mov指令的目的操作数是32位寄存器,因此会隐含零扩展到64位。

IQueryAssociations *pqa;
    HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
AssocCreate
的第一个参数是一个128位的CLSID值,由于寄存器是64位,这个CLSID被复制到堆栈里,被传递的是指向堆栈地址的指针。

010010fe movdqu  xmm0,oword ptr [CLSID_QueryAssociations (01001060)]
01001106 movdqu  oword ptr [rsp+0x60],xmm0  ; temp buffer for first parameter
0100110c lea     r8,[rsp+0x58]          ; arg3 = &pqa
01001111 lea rdx,[IID_IQueryAssociations (01001070)] ; arg2 = &IID_IQueryAssociations
01001118 lea     rcx,[rsp+0x60]         ; arg1 = &temporary
0100111d call qword ptr [_imp_AssocCreate (01001028)] ; call

movdqu
指令传递128位的值到xmmn寄存器或取128位值从xmmn寄存器,在这个例子的汇编代码中它复制CLSID到堆栈里。指向CLSID的指针传递到r8,其他两个参数传递到rcxrdx

    if (SUCCEEDED(hr)) {

01001123 test    eax,eax
01001125 jl      ReturnEAX (01001281)

这个代码检查返回值是否成功。
hr = pqa->Init(ASSOCF_INIT_BYEXENAME, pszExe, NULL, NULL);

0100112b mov     rcx,[rsp+0x58]         ; arg1 = pqa
01001130 mov     rax,[rcx]              ; rax = pqa.vtbl
01001133 xor     r14d,r14d              ; r14 = 0
01001136 mov     [rsp+0x20],r14         ; arg5 = 0
0100113b xor     r9d,r9d                ; arg4 = 0
0100113e mov     r8,rbx                 ; arg3 = pszExe
01001141 mov     r15d,0x2               ; r15 = 2 (for later)
01001147 mov     edx,r15d               ; arg2 = 2 (ASSOCF_INIT_BY_EXENAME)
0100114a call    qword ptr [rax+0x18]   ; call Init method

这是一个用c++虚函数表的间接调用。This指针传递到rcx作为第一个参数。前三个参数传递到寄存器中,最后一个参数传递到堆栈上。这个函数保留16字节空间为寄存器中参数的传递,因此第五个参数地址在rsp+0x20

if (SUCCEEDED(hr)) {

0100114d mov     ebx,eax                ; ebx = hr
0100114f test    ebx,ebx                ; FAILED?
01001151 jl      ReleasePQA (01001274)  ; jump if so
这个汇编语言代码保存返回值在ebx中,并且检查返回值是否成功。

WCHAR wszName[MAX_PATH];
            DWORD cchName = MAX_PATH;
            hr = pqa->GetString(0, ASSOCSTR_FRIENDLYAPPNAME, NULL, wszName, &cchName);
            if (SUCCEEDED(hr)) {

01001157 mov     dword ptr [rsp+0x50],0x104 ; cchName = MAX_PATH
0100115f mov     rcx,[rsp+0x58]         ; arg1 = pqa
01001164 mov     rax,[rcx]              ; rax = pqa.vtbl
01001167 lea     rdx,[rsp+0x50]         ; rdx = &cchName
0100116c mov     [rsp+0x28],rdx         ; arg6 = cchName
01001171 lea     rdx,[rsp+0xb0]         ; rdx = &wszName[0]
01001179 mov     [rsp+0x20],rdx         ; arg5 = &wszName[0]
0100117e xor     r9d,r9d                ; arg4 = 0
01001181 mov     r8d,0x4                ; arg3 = 4 (ASSOCSTR_FRIENDLYNAME)
01001187 xor     edx,edx                ; arg2 = 0
01001189 call    qword ptr [rax+0x20]   ; call GetString method
0100118c mov     ebx,eax                ; ebx = hr
0100118e test    ebx,ebx                ; FAILED?
01001190 jl      ReleasePQA (01001274)  ; jump if so

再次传递参数调用一个函数,并且测试返回值是否成功。

VARIANTARG rgvarg[2] = { 0 };

01001196 lea     rdi,[rsp+0x82]         ; rdi = &rgvarg
0100119e xor     eax,eax                ; rax = 0
010011a0 mov     ecx,0x2e               ; rcx = sizeof(rgvarg)
010011a5 rep     stosb                  ; Zero it out
x64下对一个缓冲区清零的方法和x86是相同的。

V_VT(&rgvarg[0]) = VT_BSTR;
                V_BSTR(&rgvarg[0]) = SysAllocString(wszName);
                if (V_BSTR(&rgvarg[0])) {

010011a7 mov     word ptr [rsp+0x80],0x8 ; V_VT(&rgvarg[0]) = VT_BSTR
010011b1 lea     rcx,[rsp+0xb0]         ; arg1 = &wszName[0]
010011b9 call    qword ptr [_imp_SysAllocString (01001010)] ; call
010011bf mov     [rsp+0x88],rax         ; V_BSTR(&rgvarg[0]) = result
010011c7 test    rax,rax                ; anything allocated?
010011ca je      OutOfMemory (0100126f) ; jump if failed

                    DISPPARAMS dp;
                    LONG lUnique = InterlockedIncrement(&lCounter);

010011d0 lea     rax,[lCounter (01002000)]
010011d7 mov     ecx,0x1
010011dc lock    xadd [rax],ecx             ; interlocked exchange and add
010011e0 add     ecx,0x1
InterlockedIncrement编译为机器码,lock xadd指令执行自动交换数据并且相加,最后结果存入ecx中。

                    V_VT(&rgvarg[1]) = VT_I4;
                    V_I4(&rgvarg[1]) = fUnique ? lUnique : 0;

010011e3 mov     word ptr [rsp+0x98],0x3    ; V_VT(&rgvarg[1]) = VT_I4;
010011ed mov     eax,r14d                   ; rax = 0 (r14d is still zero)
010011f0 test    r12d,r12d                  ; fUnique set?
010011f3 cmovne  eax,ecx                    ; if so, then set rax=lCounter
010011f6 mov     [rsp+0xa0],eax             ; V_I4(&rgvarg[1]) = ...
由于x64支持cmov指令,所以?:结构被编译后没有用调转指令。

dp.rgvarg = rgvarg;
                    dp.cArgs = 2;
                    dp.rgdispidNamedArgs = NULL;
                    dp.cNamedArgs = 0;

010011fd lea     rax,[rsp+0x80]             ; rax = &rgvarg[0]
01001205 mov     [rsp+0x60],rax             ; dp.rgvarg = rgvarg
0100120a mov     [rsp+0x70],r15d            ; dp.cArgs = 2 (r15 is still 2)
0100120f mov     [rsp+0x68],r14             ; dp.rgdispidNamedArgs = NULL
01001214 mov     [rsp+0x74],r14d            ; dp.cNamedArgs = 0
这段代码初始化DISPPARAMS结构剩下的成员。注意编译器重用了先前被CLSID占用的堆栈空间。

                    hr = pdisp->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dp, NULL, NULL, NULL);

01001219 mov     rax,[rsi]                  ; rax = pdisp.vtbl
0100121c mov     [rsp+0x40],r14             ; arg9 = 0
01001221 mov     [rsp+0x38],r14             ; arg8 = 0
01001226 mov     [rsp+0x30],r14             ; arg7 = 0
0100122b lea     rcx,[rsp+0x60]             ; rcx = &dp
01001230 mov     [rsp+0x28],rcx             ; arg6 = &dp
01001235 mov     word ptr [rsp+0x20],0x1    ; arg5 = 1 (DISPATCH_METHOD)
0100123c xor     r9d,r9d                    ; arg4 = 0
0100123f lea     r8,[GUID_NULL (01001080)]  ; arg3 = &IID_NULL
01001246 mov     edx,r13d                   ; arg2 = dispid
01001249 mov     rcx,rsi                    ; arg1 = pdisp
0100124c call    qword ptr [rax+0x30]       ; call Invoke method
0100124f mov     ebx,eax                    ; hr = result

这段代码设置参数并且调用 Invoke 方法。
                    VariantClear(&rgvarg[0]);
                    VariantClear(&rgvarg[1]);

01001251 lea     rcx,[rsp+0x80]             ; arg1 = &rgvarg[0]
01001259 call    qword ptr [_imp_VariantClear (01001018)]
0100125f lea     rcx,[rsp+0x98]             ; arg1 = &rgvarg[1]
01001267 call    qword ptr [_imp_VariantClear (01001018)]
0100126d jmp     ReleasePQA (01001274)
这段代码完成当前的条件分支,并跳过else分支。

                } else {
                    hr = E_OUTOFMEMORY;
                }
            }

OutOfMemory:
0100126f mov     ebx,0x8007000e             ; hr = E_OUTOFMEMORY
        pqa->Release();
ReleasePQA:
01001274 mov     rcx,[rsp+0x58]             ; arg1 = pqa
01001279 mov     rax,[rcx]                  ; rax = pqa.vtbl
0100127c call    qword ptr [rax+0x10]       ; release
else
分支

    return hr;
}
0100127f mov     eax,ebx                    ; rax = hr (for return value)
ReturnEAX:
01001281 add     rsp,0x2c0                  ; clean up the stack
01001288 pop     r15d                       ; restore
0100128a pop     r14d                       ; restore
0100128c pop     r13d                       ; restore
0100128e pop     r12d                       ; restore
01001290 pop     rdi                        ; restore
01001291 pop     rsi                        ; restore
01001292 pop     rbx                        ; restore
01001293 ret                                ; return (do not pop arguments)

返回值存储在rax中,然后在返回前恢复保存的寄存器。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

X64处理器架构 的相关文章

  • Resharper 中的警告“未使用纯方法的返回值”

    我有一个关于我正在工作的 c 项目中从 Visual Studio 中的 Resharper 收到的警告的快速问题 警告是 不使用纯方法的返回值 发生这种情况的方法如下 private static bool FilePathHasInva
  • 如何找出英特尔处理器上的指令触及了哪条高速缓存线?

    我读了这篇文章关于 Meltdown Spectre 漏洞利用 http www theregister co uk 2018 01 04 intel amd arm cpu vulnerability 允许利用 CPU 中的硬件错误从内核
  • x86 汇编乘法和除法指令操作数,16 位及更高

    我对 x86 汇编中的乘法和除法运算如何工作感到相当困惑 例如 下面的代码看起来并不太困难 因为处理的是 8 位 8 位乘法 User Input num1 20 num2 15 mov ax num1 moves the 8 bits i
  • 检查 SQL MAX() 函数返回 null

    我的问题是 如果我的表为空或者我使用 max 函数的列没有我指定的值 那么为什么 sqlDataReader hasRows TRUE 它给出了空记录 我该如何解决这个问题 提前致谢 像 MAX 这样的聚合函数将始终为每组返回一行 就您而言
  • Win32 函数获取 C:\ProgramData 的路径

    我的应用程序需要安装一些可以由应用程序在运行时编辑的文件 Installshield提供了一个别名 CommonAppDataFolder 它将在Vista和Windows 7上解析为c programData 并且也适用于Windows
  • 嵌套路径和文件集有什么区别?

    我已经在谷歌上搜索 文件集和路径之间的差异 文章有一段时间了 但没有发现任何有用的东西 例如 以下内容有什么区别 比如说 有一个someDir目录 其中包含 jar 文件且没有子目录
  • 如何让 gcc 生成合适的代码来检查缓冲区是否充满 NUL 字节?

    我正在实现一个解析磁带档案的程序 解析器逻辑的一部分是检查存档结束标记 该标记是一个充满 NUL 字节的 512 字节块 我为此编写了以下代码 希望 gcc 能对此进行很好的优化 int is eof block const char us
  • 调用/返回/jmp等后x86代码执行?

    我希望这个问题不会太愚蠢 因为它看起来似乎很明显 当我对缓冲区溢出进行一些研究时 我偶然发现了一个简单的问题 调用 返回 跳转后转到新指令地址后 CPU是否会执行该地址处的OP代码 然后将一个字节移动到下一个地址并执行下一个OP代码 依此类
  • Phonegap cordova android“项目已存在”或运行时错误

    我在这里发帖是因为这让我发疯 我正在尝试让 PhoneGap 正常工作 我安装了 ADT 捆绑包 eclipse adt 插件 android SDK gt 顺便说一句 他们真的应该更新phonegap入门教程 该教程仍然告诉您单独安装所有
  • 删除指针后将其设为 NULL 是一个好习惯吗?

    我首先要说的是 使用智能指针 您将永远不必担心这个问题 下面的代码有什么问题 Foo p new Foo use p delete p p NULL 这是由答案和评论 https stackoverflow com questions 19
  • 是否可以调用驻留在 exe 中的非导出函数?

    我想调用驻留在第 3 方 exe 中的函数并获取其结果 好像有should是一种方法 只要我知道函数地址 调用约定等 但我不知道如何 有谁知道我会怎么做 我意识到任何解决方案都是非标准的黑客 但有must成为一种方式 我的非恶意用例 我正在
  • 与 SSE 比较 16 字节字符串

    我有 16 字节的 字符串 它们可能更短 但您可能会假设它们在末尾用零填充 但您可能不会假设它们是 16 字节对齐的 至少不总是 如何编写一个例程将它们与 SSE 内在函数进行比较 是否相等 我发现这个代码片段可能会有帮助 但我不确定它是否
  • 段寄存器如何参与内存地址转换?

    到目前为止我所学到的有关细分的知识 虚拟地址包含段选择器和偏移量 段选择器与GDTR配合使用 查找段描述符的线性地址 段描述符保存有关所选段的信息 包括其线性地址 所以 我的问题是 根据我所读到的内容 虚拟地址被加载到段寄存器中 然后以某种
  • 查询外键列可以为NULL的地方

    我想获取数据 如果orgid 2或者如果根本没有行uid orgid is an integer 我能想到的最接近的事情就是做IS NULL但我没有得到数据uid没有一个orgid排 任何想法 select u uid u fname u
  • SQL - != 'NULL' 的解释

    我的SSMS代码如下 Select top 50 From FilteredContact Where statuscode 1 and emailaddress1 NULL and telephone1 NULL and address1
  • Javascript 假值(null、未定义、false、空字符串:“”或 '' 和 0)和比较(==)运算符 [重复]

    这个问题在这里已经有答案了 当我使用任何一个值时 null undefined false 0 in a if陈述 它总是被评估为谬误 false 另外 这些值的否定 null undefined false 0 in a if语句总是被评
  • Python 2.7从非默认目录打开多个文件(对于opencv)

    我在 64 位 win7 上使用 python 2 7 并拥有 opencv 2 4 x 当我写 cv2 imread pic 时 它会在我的默认 python 路径中打开 pic 即C Users Myname 但是我如何设法浏览不同的目
  • 函数地址不是实际代码地址

    在 Visual Studio 2008 C 中调试一些代码时 我注意到函数指针变量中的地址不是函数本身的实际地址 这是一个外部 C 函数 int main void printaddr const char print debug sho
  • 当跳转在 32 字节上不完全对齐时,使用 MITE(传统管道)代替 DSB(微指令缓存)

    这个问题曾经是这个 现已更新 问题 https stackoverflow com questions 59883527 unrolling 1 cycle loop reduces performance by 25 on skylake
  • 如何在 x86 ASM 中将整数转换为浮点值?

    我需要将一个整数 二进制补码 乘以一个浮点常数 这是我所拥有的 data pi dd 3 14 int dd 0ah code fld pi fmul ST 1 ST 我怎样才能转换int乘以浮点值pi 你需要fild操作说明 这是一个参考

随机推荐