如何在不使用汇编的情况下为x86编写原始机器代码?

时间:2018-06-25 21:54:40

标签: x86 low-level machine-code

我希望能够编写原始机器代码,而无需汇编或任何其他种类的高级语言,而这些语言可以直接放在闪存驱动器上并运行。我已经知道要执行此操作,我需要将主引导记录标头(我已经设法手动完成)格式化到驱动器上。我已完成此操作,并成功使用代码所在驱动器的第一个扇区(在本例中为前512个字节)中的汇编代码,使一行文本显示在屏幕上。但是,我希望能够像在MBR格式化中那样将原始的十六进制代码写入驱动器,而无需诸如汇编之类的任何工具来帮助我。我知道有一种方法可以做到这一点,但是我真的找不到任何不涉及汇编的东西。在哪里可以找到有关此信息?组装时附带了谷歌搜索机器代码或x86编程,这不是我想要的。

4 个答案:

答案 0 :(得分:4)

只画图片...

首先,您将不会找到如何用机器代码编程的方法,该方法没有与之关联的程序集,这应该很明显。任何您都能找到的大多数体面的指令参考都包含一些汇编程序的汇编以及机器代码,因为您确实需要某种方法来引用某种位模式,而汇编语言就是该语言。

例如,查找nop会找到模式10010000或0x90。因此,如果要将指令nop添加到程序中,则添加字节0x90。因此,即使您回到很早的处理器,您仍然希望使用汇编语言进行编程,并用铅笔和纸进行手工汇编,然后在尝试运行该程序之前,使用DIP开关将程序记入内存。因为这很有意义。数十年后,甚至还演示了机器代码编程,尤其是使用痛苦的指令集(例如x86),您首先进行汇编,汇编,反汇编,然后再进行讨论,所以请继续:

top:
    mov ah,01h
    jmp one
    nop
    nop
one:
    add ah,01h
    jmp two
two:
    mov bx,1234h
    nop
    jmp three
    jmp three
    jmp three
three:
    nop
    jmp top

nasm -f aout so.s -o so.elf
objdump -D so.elf

00000000 <top>:
   0:   b4 01                   mov    $0x1,%ah
   2:   eb 02                   jmp    6 <one>
   4:   90                      nop
   5:   90                      nop

00000006 <one>:
   6:   80 c4 01                add    $0x1,%ah
   9:   eb 00                   jmp    b <two>

0000000b <two>:
   b:   66 bb 34 12             mov    $0x1234,%bx
   f:   90                      nop
  10:   eb 04                   jmp    16 <three>
  12:   eb 02                   jmp    16 <three>
  14:   eb 00                   jmp    16 <three>

00000016 <three>:
  16:   90                      nop
  17:   eb e7                   jmp    0 <top>

因此,仅前几条说明描述了问题以及为什么asm如此有意义...

您可以轻松地用机器代码b4 01 mov ah,01h编程的第一个代码,我们进入了文档中的mov指令重载,并找到了要注册的立即操作数。 1011wreg数据我们只有一个字节,所以它不是一个字,因此未设置该字位,我们必须查找reg才能找到ah以b4结尾且立即数为01h。没那么糟,但是现在我想跳过一些东西,那么多少东西呢?我要使用哪个跳?我是否想保持保守并使用最少的一个字节?

我看到我想跳过两条指令,我们可以轻松地查找nop来知道它们是一个字节(0x90)指令。因此,段内直接短路应按汇编程序的选择工作。 0xEB,但偏移量是多少? 0x02可以在我和我要去的地方之间跳过两个BYTES指令。

因此,您可以阅读我在intel文档中汇编的其余说明,以了解汇编器选择这些字节的原因以及原因。

现在我正在看intel 8086/8088手册,段内直接短指令对符号的注释已扩展,段内直接不说符号已扩展,尽管此时的处理器为16位,但您还有更多的细分内容,因此,通过仅阅读手册,无法访问设计工程师并且不使用调试的汇编程序作为参考,我如何知道我是否可以为最后一条指令使用16位直接跳转,向后分支?在这种情况下,汇编器选择了字节大小的偏移量,但是如果...

我使用的是16位的手动工具,但使用的是32/64位的工具,因此我必须考虑这一点,但是我可以并且确实做到了:

three:
    nop
db 0xe9,0xe7,0xff,0xff,0xff

代替jmp top。

00000016 <three>:
  16:   90                      nop
  17:   e9 e7 ff ff ff          jmp    3 <top+0x3>

对于应该为0xe9,0xe7,0xff的8086

   db 0xb4,0x01
   db 0xeb,0x02
   db 0x90
   db 0x90

现在,如果我想更改被跳过到动作中的点之一怎么办

   db 0xb4,0x01
   db 0xeb,0x02
   db 0xb4,0x11
   db 0x90

但是它现在坏了,我必须修复跳动

   db 0xb4,0x01
   db 0xeb,0x03
   db 0xb4,0x11
   db 0x90

现在将其更改为添加

   db 0xb4,0x01
   db 0xeb,0x03
   db 0x80,0xc4,0x01
   db 0x90

现在我必须再次更改跳转

   db 0xb4,0x01
   db 0xeb,0x04
   db 0x80,0xc4,0x01
   db 0x90

但是,如果我用汇编语言对那个jmp进行了编程,那么我就不必处理汇编程序就完成了。如果您的跳转恰好位于距离的尖点,然后您说该循环中还有其他跳转,情况就会变得更糟,您必须多次检查代码,以查看其他任何跳转是否是2或3或4个字节,并推动我更长的跳转从一个字节跳到另一个字节

a:
...
jmp x
...
jmp a
...
x:

当我们通过跳转x时,是否为其分配2个字节?然后进入jmp a,并为其分配两个字节,到那时,我们可能已经找出了其余的 jmp a和a之间的指令:它恰好适合两个字节的跳转。但是最终我们到了x:发现jmp x必须是3个字节,这将jmp推得太远了,现在它必须是3字节jmp,这意味着我们必须回到jmp x并调整来自jmp a的额外字节现在是三个字节,而不是假定的2个字节。

如果您想首先直接对机器代码进行编程,那么汇编器将为您完成所有工作,并且最重要的是,如何在没有自然语言注释的情况下跟踪数百种不同的指令?

所以我可以做到

    mov ah,01h
top:
    add ah,01h
    nop
    nop
    jmp top

然后

nasm so.s -o so
hexdump -C so
00000000  b4 01 80 c4 01 90 90 eb  f9                       
|.........|
00000009

或者我可以这样做:

#include <stdio.h>
unsigned char data[]={0xb4,0x01,0x80,0xc4,0x01,0x90,0x90,0xeb,0xf9};
int main ( void )
{
    FILE *fp;
    fp=fopen("out.bin","wb");
    if(fp==NULL) return(1);
    fwrite(data,1,sizeof(data),fp);
    fclose(fp);
}

我想在循环中添加一个nop:

    mov ah,01h
top:
    add ah,01h
    nop
    nop
    nop
    jmp top

vs

#include <stdio.h>
unsigned char data[]={0xb4,0x01,0x80,0xc4,0x01,0x90,0x90,0x90,0xeb,0xf8};
int main ( void )
{
    FILE *fp;
    fp=fopen("out.bin","wb");
    if(fp==NULL) return(1);
    fwrite(data,1,sizeof(data),fp);
    fclose(fp);
}

如果我真的想用机器代码编写代码,则必须执行以下操作:

unsigned char data[]={
0xb4,0x01, //top:
0x80,0xc4,0x01, //add ah,01h
0x90, //nop
0x90, //nop
0x90, //nop
0xeb,0xf8 //jmp top
};

保持理智。我使用了一些指令集,并为自己准备了一些有趣的指令集,这些指令集更容易用机器代码编程,但是使用汇编助记符在伪代码中进行注释还是更好的方法。

如果您的目标只是简单地以某种格式,裸机或其他某种Windows或Linux文件格式程序生成一些机器代码,则可以使用汇编语言,并使用从中获得的工具链的一两个步骤二进制源代码结果的汇编源。最坏的情况是您编写了一个临时程序以从工具链的输出中获取内容,并将这些位转换为其他位。您不必扔掉可用的工具来手动编写原始位,而只需重新格式化输出文件格式即可。

答案 1 :(得分:1)

如果您真正想要的是更好地理解x86机器代码,我建议您首先查看汇编器的输出,以查看对于asm源的每一行,汇编器将哪些字节组合到输出文件中。

nasm -fbin -l listing.txt foo.asm将为您提供包含原始十六进制字节和源代码行的列表,或者nasm -fbin -l/dev/stdout foo.asm | less将列表右移到文本查看器中。有关输出外观的示例,请参见codegolf.SE上的this chroma-key blend function in 13 bytes of x86 machine code I wrote

您也可以在正常创建二进制文件后反汇编它。 ndisasm适用于平面二进制文件,并产生相同格式的十六进制字节+ asm指令。其他objdump等反汇编程序也可以使用:Disassembling A Flat Binary File Using objdump

半相关:How to turn hex code into x86 instructions


Intel的x86手册完全指定了指令的编码方式:有关前缀,操作码,ModR / M +可选SIB和可选位移的详细信息,请参见the vol.2 insn set reference manual,第2章“指令格式”,以及立即。

鉴于此,您可以阅读有关如何编码的每条说明文档,例如D1 /4 (shl r/m32, 1)表示操作码字节为D1,而ModRM的/r字段必须为4。( /r字段可作为某些指令的3个附加操作码位。)

还有一个附录将操作码字节映射回说明,以及该手册的其他部分。

当然可以使用十六进制编辑器键入您手动计算的编码,从而无需使用汇编程序即可创建512字节的二进制文件。但这是没有意义的练习。


有关x86指令编码的许多怪异之处,也请参见tips for golfing in x86 machine code:例如inc/dec的完整寄存器有单字节编码(在64位模式下除外)。它当然专注于指令 length ,但是除非您坚持自己亲自查找实际编码,否则有趣的部分是哪种形式的指令具有可用的不同或特殊的编码。 objdump -d给出的有关提示问答的几个答案显示了机器代码字节和AT&T语法反汇编。

答案 2 :(得分:0)

在Python中,您可以使用子过程模块,并使用由anatoly techtonik techtonik@gmail.com创建的Public Domain程序hexdump.py,最好采用任何已编译的语言类型并同时获取原始机器代码和asm。 。

第二个是PellesC。版本9.0 C11-17在Pelles中,您只需要在调试一次之后进行第二次调试。它为您吐出机器代码和asm代码。很好,但是您不能复制和粘贴代码。您可以看到所有内容,但如果需要,则必须手动将其键入。

两者均用于开发新的编程语言。主要是因为在构建Lexical Analyzer并通过它设置机器指令时,您会看到指令停滞不前。

我写原始机器的想法是->如果您犯了一个错误,那么您将失去任何致命的错误检测或有条件的尝试,请先进行调试或调试,然后再检查它,以免损坏机器中的东西。

这正是我们拥有计算机语言的原因。在跳入编写原始代码之前,最好使用C或C ++内联ASM方法进行测试。您将需要此处找到的x86指令集。

x86 Instruction Sets 无论如何都要保持安全。

答案 3 :(得分:-1)

http://ref.x86asm.net/coder32.html

虽然我真的不懂,为什么你会这么做。