Escrevendo em Assembly para Sega Genesis #1

O primeiro artigo dedicado a escrever jogos para o clássico console Sega Genesis em Motorola 68000 Assembly.

Vamos escrever o loop infinito mais simples para a Sega. Para isso precisaremos de: um montador, um emulador com desmontador, um editor de texto favorito, um conhecimento básico da estrutura do Sega rum.

Para desenvolvimento eu uso meu próprio montador/desmontador Gen68KryBaby:

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

A ferramenta é desenvolvida em Python 3, para montagem é fornecido como entrada um arquivo com extensão .asm ou .gen68KryBabyDisasm, a saída é um arquivo com extensão .gen68KryBabyAsm.bin, que pode ser executado no emulador ou em um console real (tenha cuidado, afaste-se, o console pode explodir!)

A desmontagem de roms também é suportada, para isso você precisa enviar um arquivo rom como entrada, sem as extensões .asm ou .gen68KryBabyDisasm. O suporte ao Opcode aumentará ou diminuirá dependendo do meu interesse no tópico e da participação dos colaboradores.

Estrutura

O cabeçalho rom da Sega ocupa os primeiros 512 bytes. Ele contém informações sobre o jogo, nome, periféricos suportados, soma de verificação e outros sinalizadores do sistema. Presumo que sem título o console nem vai olhar para o rum, pensando que está incorreto, dizendo “o que você está me dando aqui?”

Depois do cabeçalho vem a sub-rotina/Reset sub-rotina, que é onde o processador m68K inicia seu trabalho. Ok, é uma questão pequena – encontrar opcodes (códigos de operação), ou seja, não fazer nada (!) e mudar para a sub-rotina no endereço da memória. Pesquisando no Google, você encontra o opcode NOP, que não faz nada, e o opcode JSR, que realiza um salto incondicional para o endereço do argumento, ou seja, simplesmente move o carro para onde solicitamos, sem nenhum capricho.

Juntando tudo

O doador de cabeçalho da rom foi um dos jogos da versão Beta, atualmente registrado como dados hexadecimais.


 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  

Exemplo completo junto com o cabeçalho da rom:

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

Coletamos a seguir:

Запускаем ром 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 Montador + C = Um Amor

Nesta nota descreverei o processo de chamada de funções C a partir do assembler.
Vamos tentar chamar printf(“Hello World!\n”); e sair(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

Tudo é muito mais simples do que parece, na seção .rodata iremos descrever dados estáticos, neste caso a linha “Hello, world!”, 10 é um caractere de nova linha, e também não esquecemos de anulá-lo.

Na seção de código declararemos as funções externas printf, exit das bibliotecas stdio, stdlib e também declararemos a função de entrada main:

    extern printf
    extern exit
    global main

Passamos 0 para o registrador de retorno da função rax, você pode usar mov rax, 0; mas para acelerar eles usam xor rax, rax; A seguir, passamos um ponteiro para a string do primeiro argumento:

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

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

Por analogia, passamos 0 para o primeiro argumento e chamamos exit:

    call exit

Como dizem os americanos:
Quem não escuta ninguém
Esse pilaf está comendo @ Alexander Pelevin

Fontes

https://www.devdungeon. com/content/how-mix-c-and-assembly
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

Código fonte

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

Olá mundo montador x86_64

Neste post irei descrever o processo de configuração do IDE, escrevendo o primeiro Hello World em assembler x86_64 para o sistema operacional Ubuntu Linux.
Vamos começar instalando o IDE SASM, montador 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

Código Hello World retirado do blog James Fisher, adaptado para montagem e depuração em SASM. A documentação do SASM afirma que o ponto de entrada deve ser uma função chamada main, caso contrário a depuração e compilação do código serão incorretas.
O que fizemos neste código? Fez uma chamada syscall – acesso ao kernel do sistema operacional Linux com argumentos corretos nos registros, um ponteiro para uma string na seção de dados.

Sob uma lupa

Vejamos o código com mais detalhes:

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: