为 Sega Genesis #1 编写汇编

第一篇专门为 Motorola 68000 Assembly 中的经典 Sega Genesis 控制台编写游戏的文章。

让我们为 Sega 编写最简单的无限循环。为此,我们需要:一个汇编器、一个带有反汇编器的模拟器、一个最喜欢的文本编辑器、对 Sega rum 结构的基本了解。

对于开发,我使用自己的汇编器/反汇编器 Gen68KryBaby:

https://gitlab.com/demensdeum/gen68krybaby/

该工具是用 Python 3 开发的,用于汇编时提供扩展名为 .asm 或 .gen68KryBabyDisasm 的文件作为输入,输出是扩展名为 .gen68KryBabyAsm.bin 的文件,该文件可以在模拟器或计算机上运行一个真正的控制台(小心,走开,控制台可能会爆炸!)

还支持反汇编 rom,为此您需要提交一个 rom 文件作为输入,不带 .asm 或 .gen68KryBabyDisasm 扩展名。操作码支持将根据我对该主题的兴趣和贡献者的参与而增加或减少。

结构

Sega ROM 标头占据前 512 个字节。它包含有关游戏、名称、支持的外围设备、校验和以及其他系统标志的信息。我认为如果没有标题,控制台甚至不会看朗姆酒,认为它是不正确的,并说“你在这里给我什么?”

标头之后是子例程/重置子例程,这是 m68K 处理器开始工作的地方。好吧,这是一件小事……查找操作码(操作码),即什么都不做(!)并切换到内存中地址处的子程序。通过谷歌搜索,您可以找到 NOP 操作码,它不执行任何操作,以及 JSR 操作码,它执行无条件跳转到参数地址,也就是说,它只是将回车移动到我们要求的位置,没有任何突发奇想。

将它们放在一起

ROM 的标头捐赠者是 Beta 版本中的游戏之一,目前记录为十六进制数据。


 00 ff 2b 52 00 00 02 00 00 00 49 90 00 00 49 90 00 00 49 90 00...и т.д. 

Код программы со-но представляет из себя объявление сабрутины Reset/EntryPoint в 512 (0x200) байте, NOP, возврат каретки к 0x00000200, таким образом мы получим бесконечный цикл.

Ассемблерный код сабрутины Reset/EntryPoint:

    NOP
    NOP
    NOP 
    NOP
    NOP
    JSR 0x00000200  

完整示例以及 rom 标头:

https://gitlab.com /demensdeum/segagenesisamples/-/blob/main/1InfiniteLoop/1infiniteloop.asm

我们接下来收集:

Запускаем ром 1infiniteloop.asm.gen68KryBabyAsm.bin в режиме дебаггера эмулятора Exodus/Gens, смотрим что m68K корректно считывает NOP, и бесконечно прыгает к EntryPoint в 0x200 на JSR

Здесь должен быть Соник показывающий V, но он уехал на Вакен.

Ссылки

https://gitlab.com/demensdeum/gen68krybaby/

https://gitlab.com/demensdeum/segagenesissamples

https://www.exodusemulator.com/downloads/release-archive

Источники

ROM Hacking Demo – Genesis and SNES games in 480i

http://68k.hax.com/

https://www.chibiakumas.com/68000/genesis.php

https://plutiedev.com/rom-header

https://blog.bigevilcorporation.co.uk/2012/02/28/sega-megadrive-1-getting-started/

https://opensource.apple.com/source/cctools/cctools-836/as/m68k-opcode.h.auto.html

x86_64 汇编器 + C = 一份爱

在这篇笔记中我将描述从汇编程序调用 C 函数的过程。
让我们尝试调用 printf(“Hello World!\n”);并退出(0);

    message: db "Hello, world!", 10, 0

section .text
    extern printf
    extern exit
    global main

main:
    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

一切都比看起来简单得多,在 .rodata 部分我们将描述静态数据,在本例中为“Hello, world!”行,10 是换行符,我们也不要忘记将其设为 null。

在代码部分,我们将声明外部函数 printf、stdio、stdlib 库的 exit,并且我们还将声明输入函数 main:

    extern printf
    extern exit
    global main

我们从rax函数中将0传递给返回寄存器,您可以使用mov rax, 0;但为了加快速度,他们使用 xor rax, rax;接下来,我们将指向字符串的指针传递给第一个参数:

Далее вызываем внешнюю функцию Си printf:

    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

以此类推,我们将 0 传递给第一个参数并调用 exit:

    call exit

正如美国人所说:
谁不听谁的
那抓饭正在吃@ Alexander Pelevin

来源

https://www.devdungeon。 com/content/how-mix-c-and-assemble
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html

源代码

https://gitlab.com/demensdeum/ assembly-playground

Hello World x86_64 汇编器

在这篇文章中,我将描述设置 IDE 的过程,为 Ubuntu Linux 操作系统在 x86_64 汇编程序中编写第一个 Hello World。
让我们从安装 SASM IDE、nasm 汇编器开始:

Далее запустим SASM и напишем Hello World:


section .text

main:
    mov rbp, rsp      ; for correct debugging
    mov rax, 1        ; write(
    mov rdi, 1        ;   STDOUT_FILENO,
    mov rsi, msg      ;   "Hello, world!\n",
    mov rdx, msglen   ;   sizeof("Hello, world!\n")
    syscall           ; );

    mov rax, 60       ; exit(
    mov rdi, 0        ;   EXIT_SUCCESS
    syscall           ; );

section .rodata
    msg: db "Hello, world!"
    msglen: equ $-msg

Hello World 代码取自博客 James Fisher,适用于 SASM 中的汇编和调试。 SASM文档规定,入口点必须是名为main的函数,否则代码的调试和编译将不正确。
我们在这段代码中做了什么?拨打了系统调用–使用寄存器中的正确参数、指向数据部分中的字符串的指针来访问 Linux 操作系统内核。

放大镜下

让我们更详细地看一下代码:

global – директива ассемблера позволяющая задавать глобальные символы со строковыми именами. Хорошая аналогия – интерфейсы заголовочных файлов языков C/C++. В данном случае мы задаем символ main для функции входа.

section – директива ассемблера позволяющая задавать секции (сегменты) кода. Директивы section или segment равнозначны. В секции .text помещается код программы.

Обьявляем начало функции main. В ассемблере функции называются подпрограммами (subroutine)

Первая машинная команда mov – помещает значение из аргумента 1 в аргумент 2. В данном случае мы переносим значение регистра rbp в rsp. Из комментария можно понять что эту строку добавил SASM для упрощения отладки. Видимо это личные дела между SASM и дебаггером gdb.

Далее посмотрим на код до сегмента данных .rodata, два вызова syscall, первый выводит строку Hello World, второй обеспечивает выход из приложения с корректным кодом 0.

Представим себе что регистры это переменные с именами rax, rdi, rsi, rdx, r10, r8, r9. По аналогии с высокоуровневыми языками, перевернем вертикальное представление ассемблера в горизонтальное, тогда вызов syscall будет выглядеть так:

Тогда вызов печати текста:

Вызов exit с корректным кодом 0:

Рассмотрим аргументы подробнее, в заголовочном файле asm/unistd_64.h находим номер функции __NR_write – 1, далее в документации смотрим аргументы для write:
ssize_t write(int fd, const void *buf, size_t count);

Первый аргумент – файловый дескриптор, второй – буфер с данными, третий – счетчик байт для записи в дескриптор. Ищем номер файлового дескриптора для стандартного вывода, в мануале по stdout находим код 1. Далее дело за малым, передать указатель на буфер строки Hello World из секции данных .rodata – msg, счетчик байт – msglen, передать в регистры rax, rdi, rsi, rdx корректные аргументы и вызвать syscall.

Обозначение константных строк и длины описывается в мануале nasm: