Забавные новости 0й-Ti :)

Тема в разделе "WASM.HEAP", создана пользователем UbIvItS, 18 июн 2018.

Статус темы:
Закрыта.
  1. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    603
    Более низкий уровень погромиста асматика, это микрокод процессора, а так же погромирования железа материнки. Кстати, оказывается все эти аппаратные трояны водятся в железе матки.
     
  2. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Ох, я так и знал, что равнодушных к этому видосу тут почти не будет))
     
    CaptainObvious нравится это.
  3. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.000
    Удивительно, что петросянские гавновидосы, которые ты постишь, до сих пор кто-то смотрит?
     
  4. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Вот лично в тебе я никогда по этому поводу не сомневался, не переживай.
     
  5. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Вы сами напросились! Сегодня мы будем совмещать неприятное с бесполезным: мы будем писать настоящую грязь!

    В прошлый раз мы писали 32х-битные ядра, сегодня пройдёмся по геймдеву и напишем змейку, которая поместится в один загрузочный сектор (в 512 байт) и будет загружаться в 16-битном реальном режиме.

    Загрузочный сектор должен соответствовать формату MBR (Main Boot Record), который требует, чтобы два последних байта содержали сигнатуру 0xAA55.
    А значит, из 512 байт для нашего кода доступно 510. Ветер нам благоприятствует, а большего и не надо!

    Собираем кастомный тулчейн для сборки под 16-битный реальный режим.
    Создаём в корне i8086-unknown-none.json:
    Код (Text):
    1. {
    2.     "arch": "x86",
    3.     "cpu": "i386",
    4.     "data-layout": "e-m:e-p:32:32-n8:16:32-S128",
    5.     "dynamic-linking": false,
    6.     "executables": true,
    7.     "linker-flavor": "ld.lld",
    8.     "linker": "rust-lld",
    9.     "llvm-target": "i386-unknown-none-code16",
    10.     "max-atomic-width": 64,
    11.     "position-independent-executables": false,
    12.     "disable-redzone": true,
    13.     "target-c-int-width": "32",
    14.     "target-pointer-width": "32",
    15.     "target-endian": "little",
    16.     "panic-strategy": "abort",
    17.     "os": "none",
    18.     "vendor": "unknown",
    19.     "relocation-model": "static"
    20. }
    Формат data-layout описан в документации LLVM.

    Подключаем тулчейн в .cargo/config.toml:
    Код (Text):
    1. [unstable]
    2. build-std = ["core", "compiler_builtins"]
    3. build-std-features = ["compiler-builtins-mem"]
    4.  
    5. [build]
    6. target = "i8086-unknown-none.json"
    Переключаемся на nightly-ветку, создав в корне rust-toolchain.toml:
    Код (Text):
    1. [toolchain]
    2. channel = "nightly"
    3. components = ["rust-src", "rust-std", "rustc", "cargo"]
    Так как мы ограничены размером, в Cargo.toml отключаем рантйм-проверки и ставим оптимизации по размеру.
    Код (Text):
    1. [package]
    2. name = "snake"
    3. version = "0.1.0"
    4. edition = "2021"
    5.  
    6. [profile.dev]
    7. panic = "abort"
    8.  
    9. [profile.release]
    10. opt-level = "s"
    11. overflow-checks = false
    12. panic = "abort"
    13. debug = false
    14. strip = "debuginfo"
    15. debug-assertions = false
    16. codegen-units = 1
    17. lto = false
    Разложим будущие код и данные в бутсекторе, написав линкер-скрипт.
    Создаём в корне bootsector.ld:
    Код (Text):
    1.  
    2. ENTRY(_start)
    3. SECTIONS {
    4.     . = 0x500;
    5.     _stack_start = .;
    6.     . = 0x7c00;
    7.     _stack_end = .;
    8.     _mbr_start = .;
    9.     .text :
    10.     {
    11.         *(.text .text.*)
    12.     }
    13.     .rodata :
    14.     {
    15.         *(.rodata .rodata.*)
    16.     }
    17.     .data :
    18.     {
    19.         *(.rodata .rodata.*)
    20.         *(.data .data.*)
    21.         *(.got .got.*)
    22.     }
    23.     _mbr_end = .;
    24.     . = 0x7c00 + 510;
    25.     .magic_number :
    26.     {
    27.         SHORT(0xaa55)
    28.     }
    29. }
    30.  
    Синтаксис линкер-скриптов описан в документации на ld.

    Во время инициализации BIOS раскладывает код и данные по фиксированным адресам, которые можно посмотреть здесь.
    BIOS всегда загружает бутсектор по адресу 0x7C00 - а значит, надо уместить всю нашу игру, включая сигнатуру MBR, в диапазоне [0x7C00..0x7DFF].
    Наш линкер-скрипт кладёт секцию кода по адресу 0x7C00. Следом идут данные, и в последние два байта пишем сигнатуру.

    Переходим к коду:
    Код (Rust):
    1. #![no_std]
    2. #![no_main]
    3. #![warn(clippy::pedantic)]
    4.  
    5. use core::panic::PanicInfo;
    6.  
    7. core::arch::global_asm!(r#"
    8.    .global _start
    9.  
    10.    _start:
    11.        # Setup the segment for the screen buffer:
    12.        mov ax, 0xB800
    13.        mov es, ax
    14.  
    15.        # Print something onto the screen:
    16.        mov ax, 0xA0A0
    17.        mov es:[0], ax
    18.  
    19.        call main
    20. "#);
    21.  
    22. #[no_mangle]
    23. extern "stdcall" fn main() -> ! {
    24.     loop {}
    25. }
    26.  
    27. #[panic_handler]
    28. fn panic(_info: &PanicInfo) -> ! {
    29.     loop {}
    30. }
    Собираем и запускаем:
    Код (Text):
    1.  
    2. cargo build --release
    3. objcopy -I elf32-i386 -O binary ./kernel
    4. qemu-system-x86_64 --drive format=raw,file=target/i8086-unknown-none/release/kernel
    5.  
    upload_2024-1-26_23-5-53.png
    It's alive! Мы видим зелёную А с хвостиком в первой ячейке консоли.
    Чтобы понять, что здесь происходит, вспомним сегментную модель памяти в реальном режиме.

    Вся память разделена на сегменты по 16 байт. Адрес формируется из двух частей - 16-битного номера сегмента и 16-битного смещения относительно него:

    SegmentationRealMode.png

    Такая модель позволяет адресовать 0xFFFF * 16 + 0xFFFF байт - примерно один мегабайт.

    Как следует из карты памяти, консольный буфер лежит по адресу 0xB8000. Так как адрес больше 16 бит, мы не можем положить его в регистр и обращаться к нему напрямую.
    Однако, мы можем найти номер сегмента, в котором он лежит, поделив адрес на 16: получится сегмент с номером 0xB800.
    Теперь мы можем положить его в любой сегментный регистр и обращаться к этому буферу по смещению относительно выбранного сегмента.

    Консольный буфер имеет размер 80x25 ячеек, где каждая ячейка представляет собой двухбайтовую структуру, где первый байт - байт символа, а второй байт - байт цветов шрифта и фона.

    ScreenBuffer.png

    Выведем что-то на экран:
    Код (ASM):
    1. ; Задаём сегмент консольного буфера:
    2. mov ax, 0xB800
    3. mov es, ax
    4.  
    5. ; Выведем букву на экран:
    6. mov ah, 0xA0 ; Задаём цвет
    7. mov al, 'A' ; Задаём букву
    8. mov es:[10 * 2], ax ; Пишем букву с цветом по координатам [10, 0]
    На расте повторить это сложнее: он не предполагает работу с сегментной моделью, поэтому придётся использовать ассемблерные вставки, чтобы адресовать сегмент es.
    Код (Rust):
    1. #![no_std]
    2. #![no_main]
    3. #![warn(clippy::pedantic)]
    4.  
    5. use core::panic::PanicInfo;
    6.  
    7. core::arch::global_asm!(r#"
    8.    .global _start
    9.  
    10.    _start:
    11.        cli
    12.  
    13.        # Setup the stack and the data segment:
    14.        xor ax, ax
    15.        mov ss, ax
    16.        mov ds, ax
    17.        mov sp, 0x7C00
    18.  
    19.        # Setup the screen segment:
    20.        mov ax, 0xB800
    21.        mov es, ax
    22.  
    23.        call main
    24. "#);
    25.  
    26. mod vga {
    27.     pub fn fill(color: u8) {
    28.         unsafe { core::arch::asm!(
    29.             "xor di, di",
    30.             "rep stosw",
    31.             in("ax") (color as u16) << 8,
    32.             in("cx") 80 * 25,
    33.             out("di") _
    34.         ) };
    35.     }
    36.  
    37.     pub fn draw(pos: usize, color: u8, sym: char) {
    38.         let cell: u16 = ((color as u16) << 8) | (sym as u8 as u16);
    39.         unsafe { core::arch::asm!(
    40.             "mov es:[di], ax",
    41.             in("di") pos * 2,
    42.             in("ax") cell
    43.         ) };
    44.     }
    45.  
    46.     pub fn text(pos: usize, color: u8, text: &str) {
    47.         for (index, ch) in text.as_bytes().iter().enumerate() {
    48.             self::draw(pos + index, color, *ch as char);
    49.         }
    50.     }
    51. }
    52.  
    53. #[no_mangle]
    54. extern "stdcall" fn main() -> ! {
    55.     vga::fill(0x10);
    56.  
    57.     for i in 0..=10 {
    58.         let sym = ('a' as usize + i) as u8;
    59.         vga::draw(i, 0xA0, sym as char);
    60.     }
    61.  
    62.     vga::text(80, 0xB0, "Sample text");
    63.  
    64.     loop {}
    65. }
    66.  
    67. #[panic_handler]
    68. fn panic(_info: &PanicInfo) -> ! {
    69.     loop {}
    70. }
    upload_2024-1-26_23-2-12.png

    Посмотрим, во что превратился наш код:

    16Bit.png

    Мы видим две вещи:
    1. Компилятор генерирует 32х-битный код.
    2. Почти у каждой инструкции есть префикс 0x66 (Override operand size), который переключает инструкцию на работу с 32х-битными операндами.

    Префиксы нам мешают, так как съедают драгоценное место.

    LLVM не поддерживает генерацию 16-битного кода и для работы в реальном режиме использует 32х-битный код с префиксами.
    Избавиться от префиксов можно только одним способом: переключиться в защищённый режим и использовать родной 32х-битный код, которому префиксы уже не нужны.
    Мы потратим несколько байт на переключение, но отыграем их на более кратком сгенерированном коде.

    Чтобы переключиться в защищённый режим, необходимо настроить глобальную таблицу дескрипторов, выделив сегменты для 32х-битного кода и данных.
    Затем в регистре CR0 необходимо включить бит PE (Protection Enabled) и сразу сделать межсегментный переход на созданный ранее 32х-битный сегмент кода.

    Segmentation.png
    Код (ASM):
    1.  
    2. .global _start
    3. .data
    4.  
    5. .align 4
    6. _GDT:
    7.     # The first entry must be empty:
    8.     .4byte 0x00000000
    9.     .4byte 0x00000000
    10.  
    11.     # Code entry (8):
    12.     .4byte 0x0000FFFF
    13.     .4byte 0x005F9F00
    14.  
    15.     # Data entry (16):
    16.     .4byte 0x0000FFFF
    17.     .4byte 0x00CF9200
    18.  
    19. _GDTR:
    20.     .2byte 23    # Limit (sizeof(_GDT) - 1)
    21.     .4byte _GDT  # Base (linear address of the GDT)
    22.  
    23. .text
    24.  
    25. .code16
    26. _start:
    27.     cli
    28.  
    29.     mov ecx, offset _GDTR
    30.     lgdt [ecx]
    31.  
    32.     # Set Protection Enable bit:
    33.     mov eax, cr0
    34.     or al, 1
    35.     mov cr0, eax
    36.  
    37.     ljmp 0x8, offset _protected_mode
    38.  
    39. .code32
    40. _protected_mode:
    41.     mov ax, 16
    42.     mov ds, ax
    43.     mov es, ax
    44.     mov ss, ax
    45.     mov esp, 0x7C00  # The end of the conventional memory [0x500..0x7BFF)
    46.     ...
    47.  
    Итак, мы в защищённом режиме.
    На этом этапе у нас больше нет прерываний BIOS, а старая 16-битная таблица прерываний уже невалидна.
    Так как мы хотим получать ввод с клавиатуры, придётся завести новую IDT.

    Interrupts.png

    Так как дескрипторы для всех 256 прерываний нам хранить негде, сгенерируем таблицу на лету из шаблона:
    Код (ASM):
    1. .data
    2.  
    3. .align 4
    4. _IDT_entry_template:
    5.     .2byte 0    # ISR low
    6.     .2byte 8    # Segment selector
    7.     .byte  0    # Reserved
    8.     .byte  0x8E # Flags
    9.     .2byte 0    # ISR high
    10.  
    11. _IDTR:
    12.     .2byte 2047    # Limit
    13.     .4byte 0x7E00  # Base
    14.  
    15. .text
    16.     # Create IDT with 256 entries:
    17.     mov esi, offset _IDT_entry_template
    18.     mov eax, [esi]
    19.     mov edx, [esi + 4]
    20.     mov esi, offset _isr
    21.     or ax, si
    22.     xor si, si
    23.     or edx, esi
    24.     mov edi, 0x7E00  # The beginning of the IDT
    25.     xor ecx, ecx
    26. 2:
    27.     mov [edi + ecx * 8], eax
    28.     mov [edi + ecx * 8 + 4], edx
    29.     dec cl
    30.     jnz 2b
    31.  
    32.     mov ecx, offset _IDTR
    33.     lidt [ecx]
    34.  
    35. _isr:
    36.     iretd
    37.  
    Нас интересуют только клавиатурные прерывания PS/2 от контроллера 8042.
    Включим их в контроллере прерываний 8259: клавиатурному прерыванию соответствует IRQ1, включим его в Master PIC (детали см. здесь):
    Код (ASM):
    1.  
    2.     # Enable interrupts for the keyboard only:
    3.     mov al, 0xFD
    4.     out 0x21, al # Master PIC data
    5.     mov al, 0xFF
    6.     out 0xA1, al # Slave PIC data
    7.     sti
    8.     ...
    9.  
    10. _isr:
    11.     pushad
    12.  
    13.     # Read the key press:
    14.     in al, 0x60
    15.     mov [0x9999], al  # Store to the hardcoded address
    16.  
    17.     # Send EOI (End-of-Interrupt):
    18.     mov al, 0x20
    19.     out 0x20, al
    20.  
    21.     popad
    22.     iretd
    23.  
    Вот и всё, можем писать игру.
    Так как мы пишем уже 32х-битный код, переключимся на 32х-битный тулчейн.
    Создаём i686-unknown-none.json:
    Код (Text):
    1.  
    2. {
    3.     "llvm-target": "i686-unknown-none",
    4.     "data-layout": "e-m:e-Fi8-i8:8:8-i16:8:8-i32:8:8-f80:128-n8:16:32-S128-p:32:8:8",
    5.     "arch": "x86",
    6.     "target-endian": "little",
    7.     "target-pointer-width": "32",
    8.     "target-c-int-width": "32",
    9.     "os": "none",
    10.     "executables": true,
    11.     "linker-flavor": "ld.lld",
    12.     "linker": "rust-lld",
    13.     "panic-strategy": "abort",
    14.     "disable-redzone": true,
    15.     "features": "+soft-float,-sse"
    16. }
    17.  
    Прописываем его в .cargo/config.toml и можем писать код.
    Для оптимизации мы будем использовать байты букв в консольном буфере для хранения направлений на следующую ячейку змеи.
    Цвет этих "букв" мы сделаем равным цвету фона ячейки, чтобы содержимое байтов букв не было видно.

    Snake.png

    Код (Rust):
    1.  
    2. #![no_std]
    3. #![no_main]
    4. #![warn(clippy::pedantic)]
    5.  
    6. //
    7. // 0x0000: Interrupt Vector Table
    8. // 0x0400: BIOS data area
    9. // 0x0500: Conventional memory
    10. // ...
    11. // ↑ Stack
    12. // 0x7C00: _start // Bootstrap code area (446 bytes)
    13. // ↓ Code
    14. // ...
    15. // MBR Partition Table beginning:
    16. // 0x7DBE: +0: Partition 1 Entry
    17. //         +8: Partition 2 Entry
    18. //        +16: Partition 3 Entry
    19. //        +24: Partition 4 Entry
    20. // 0x7DFE: 0xAA55 Boot signature
    21. // 0x7E00: Conventional memory
    22. // ...
    23. // 0x80000: Extended BIOS Data Area
    24. // ...
    25. // 0xA0000: Video display memory
    26. // ...
    27. // 0xB8000: Text screen video memory
    28. // ...
    29. // 0xC0000: Video BIOS
    30. // ...
    31. // 0xC8000: BIOS expansion
    32. // ...
    33. // 0xF0000..0xFFFFF: Motherboard BIOS
    34. //
    35.  
    36. core::arch::global_asm!(r#"
    37.    .global _start
    38.  
    39.    .data
    40.  
    41.    .align 4
    42.    _GDT:
    43.        # The first entry must be empty:
    44.        .4byte 0x00000000
    45.        .4byte 0x00000000
    46.  
    47.        # Code entry (8):
    48.        .4byte 0x0000FFFF
    49.        .4byte 0x005F9F00
    50.  
    51.        # Data entry (16):
    52.        .4byte 0x0000FFFF
    53.        .4byte 0x00CF9200
    54.  
    55.    _GDTR:
    56.        .2byte 23    # Limit (sizeof(_GDT) - 1)
    57.        .4byte _GDT  # Base (linear address of the GDT)
    58.  
    59.  
    60.    .align 4
    61.    _IDT_entry_template:
    62.        .2byte 0    # ISR low
    63.        .2byte 8    # Segment selector
    64.        .byte  0    # Reserved
    65.        .byte  0x8E # Flags
    66.        .2byte 0    # ISR high
    67.  
    68.    _IDTR:
    69.        .2byte 2047    # Limit
    70.        .4byte 0x7E00  # Base
    71.  
    72.    .text
    73.  
    74.    .code16
    75.    _start:
    76.        cli
    77.  
    78.        mov ecx, offset _GDTR
    79.        lgdt [ecx]
    80.  
    81.        # Set Protection Enable bit:
    82.        mov eax, cr0
    83.        or al, 1
    84.        mov cr0, eax
    85.  
    86.        ljmp 0x8, offset _protected_mode
    87.  
    88.    .code32
    89.    _protected_mode:
    90.        mov ax, 16
    91.        mov ds, ax
    92.        mov es, ax
    93.        mov ss, ax
    94.        mov esp, 0x7C00  # The end of the conventional memory [0x500..0x7BFF)
    95.  
    96.        # Create IDT with 256 entries:
    97.        mov esi, offset _IDT_entry_template
    98.        mov eax, [esi]
    99.        mov edx, [esi + 4]
    100.        mov esi, offset _isr
    101.        or ax, si
    102.        xor si, si
    103.        or edx, esi
    104.        mov edi, 0x7E00  # The beginning of the IDT
    105.        xor ecx, ecx
    106.    2:
    107.        mov [edi + ecx * 8], eax
    108.        mov [edi + ecx * 8 + 4], edx
    109.        dec cl
    110.        jnz 2b
    111.  
    112.        mov ecx, offset _IDTR
    113.        lidt [ecx]
    114.  
    115.        # Enable interrupts for the keyboard only:
    116.        mov al, 0xFD
    117.        out 0x21, al
    118.        mov al, 0xFF
    119.        out 0xA1, al
    120.        sti
    121.  
    122.        call main
    123.  
    124.    _isr:
    125.        pushad
    126.  
    127.        in al, 0x60
    128.        mov [0x9999], al
    129.        mov al, 0x20
    130.        out 0x20, al
    131.  
    132.        popad
    133.        iretd
    134. "#);
    135.  
    136.  
    137. mod vga {
    138.     use core::sync::atomic::{AtomicU16, Ordering};
    139.  
    140.     pub const ADDR: usize = 0xB8000;
    141.     pub const WIDTH: usize = 80;
    142.     pub const HEIGHT: usize = 25;
    143.  
    144.     pub mod cell {
    145.         #[repr(C)]
    146.         #[derive(Clone, Copy)]
    147.         pub struct Layout {
    148.             sym: i8,
    149.             color: u8
    150.         }
    151.  
    152.         #[repr(C)]
    153.         #[derive(Clone, Copy)]
    154.         pub union Cell {
    155.             raw: u16,
    156.             layout: Layout
    157.         }
    158.  
    159.         impl Cell {
    160.             pub const fn new(color: u8, sym: i8) -> Self {
    161.                 Self { layout: Layout { sym, color } }
    162.             }
    163.  
    164.             pub const fn from_raw(raw: u16) -> Self {
    165.                 Self { raw }
    166.             }
    167.  
    168.             pub const fn as_raw(self) -> u16 {
    169.                 unsafe { self.raw }
    170.             }
    171.  
    172.             pub fn sym(self) -> i8 {
    173.                 unsafe { self.layout.sym }
    174.             }
    175.         }
    176.     }
    177.  
    178.     pub use cell::Cell;
    179.  
    180.     pub fn screen() -> *mut Cell {
    181.         self::ADDR as *mut Cell
    182.     }
    183.  
    184.     pub fn screen_atomic() -> *mut AtomicU16 {
    185.         self::ADDR as *mut AtomicU16
    186.     }
    187.  
    188.     pub fn cell(pos: usize) -> &'static mut Cell { // '
    189.         unsafe { &mut *self::screen().add(pos) }
    190.     }
    191.  
    192.     pub fn atomic_cell(pos: usize) -> &'static mut AtomicU16 { // '
    193.         unsafe { &mut *self::screen_atomic().add(pos) }
    194.     }
    195.  
    196.     pub fn set(pos: usize, cell: Cell) {
    197.         *self::cell(pos) = cell;
    198.     }
    199.  
    200.     pub fn swap(pos: usize, cell: Cell) -> Cell {
    201.         Cell::from_raw(self::atomic_cell(pos).swap(cell.as_raw(), Ordering::Relaxed))
    202.     }
    203.  
    204.     pub fn fill(offset: usize, count: usize, cell: Cell) {
    205.         unsafe { core::arch::asm!(
    206.             "rep stosw",
    207.             in("ax") cell.as_raw(),
    208.             inlateout("ecx") count => _,
    209.             inlateout("edi") ADDR + (offset << 1) => _,
    210.             options(nostack)
    211.         ) };
    212.     }
    213.  
    214.     pub fn clear() {
    215.         unsafe { core::arch::asm!(
    216.             "xor al, al",
    217.             "rep stosb",
    218.             out("al") _,
    219.             inlateout("ecx") (WIDTH * HEIGHT) << 1 => _,
    220.             inlateout("edi") ADDR => _,
    221.             options(nostack)
    222.         ) };
    223.     }
    224.  
    225.     #[allow(dead_code)]
    226.     #[repr(u8)]
    227.     pub enum Color {
    228.         Black,
    229.         Blue,
    230.         Green,
    231.         Cyan,
    232.         Red,
    233.         Magenta,
    234.         Brown,
    235.         White,
    236.         Gray,
    237.         LightBlue,
    238.         LightGreen,
    239.         LightCyan,
    240.         LightRed,
    241.         LightMagenta,
    242.         Yellow,
    243.         BrightYellow
    244.     }
    245.  
    246.     impl Color {
    247.         pub const fn raw(background: Color, text: Color) -> u8 {
    248.             (background as u8) << 4 | (text as u8)
    249.         }
    250.     }
    251. }
    252.  
    253. mod cpu {
    254.     pub fn rand() -> usize {
    255.         let low: usize;
    256.         unsafe { core::arch::asm!(
    257.             "rdtsc",
    258.             out("edx") _,
    259.             out("eax") low,
    260.             options(nomem, nostack)
    261.         ) };
    262.         low
    263.     }
    264. }
    265.  
    266. mod input {
    267.     use core::sync::atomic::{AtomicU8, Ordering};
    268.  
    269.     pub const SCAN_UP   : u8 = 0x48;
    270.     pub const SCAN_DOWN : u8 = 0x50;
    271.     pub const SCAN_LEFT : u8 = 0x4B;
    272.     pub const SCAN_RIGHT: u8 = 0x4D;
    273.  
    274.     pub const ADDR: usize = 0x9999;
    275.  
    276.     pub type Scancode = u8;
    277.  
    278.     fn scancode() -> &'static mut AtomicU8 { // '
    279.         unsafe { &mut *(self::ADDR as *mut AtomicU8) }
    280.     }
    281.  
    282.     pub fn read() -> Scancode {
    283.         scancode().swap(0, Ordering::Relaxed)
    284.     }
    285. }
    286.  
    287. mod snake {
    288.     use crate::vga;
    289.     use crate::cpu;
    290.  
    291.     const BACKGROUND_COLOR: u8 = vga::Color::raw(vga::Color::Black, vga::Color::Black);
    292.     const SNAKE_COLOR: u8 = vga::Color::raw(vga::Color::LightGreen, vga::Color::LightGreen);
    293.     const FOOD_COLOR: u8 = vga::Color::raw(vga::Color::LightBlue, vga::Color::LightBlue);
    294.  
    295.     #[non_exhaustive]
    296.     #[repr(i8)]
    297.     #[derive(Clone, Copy, PartialEq, Eq)]
    298.     pub enum CellContent {
    299.         None = 0,
    300.         Left = -1,
    301.         Right = 1,
    302.         Up = -(vga::WIDTH as i8),
    303.         Down = (vga::WIDTH as i8),
    304.         Food = 0x7F
    305.     }
    306.  
    307.     impl CellContent {
    308.         pub const fn as_raw(self) -> i8 {
    309.             self as i8
    310.         }
    311.     }
    312.  
    313.     pub struct Snake {
    314.         head: usize,
    315.         tail: usize
    316.     }
    317.  
    318.     impl Snake {
    319.         pub const fn new() -> Self {
    320.             Self { head: 0, tail: 0 }
    321.         }
    322.  
    323.         fn generate_food_pos() -> usize {
    324.             cpu::rand() % (vga::WIDTH * vga::HEIGHT)
    325.         }
    326.  
    327.         fn spawn_food() {
    328.             let food_pos = Self::generate_food_pos();
    329.             vga::set(food_pos, vga::Cell::new(FOOD_COLOR, CellContent::Food.as_raw()));
    330.         }
    331.  
    332.         pub fn init(&mut self) {
    333.             const INITIAL_LENGTH: usize = 5;
    334.             const INITIAL_DIRECTION: CellContent = CellContent::Right;
    335.  
    336.             let to_abs = |x, y| -> usize {
    337.                 y * vga::WIDTH + x
    338.             };
    339.  
    340.             self.head = to_abs(vga::WIDTH / 2, vga::HEIGHT / 2);
    341.             self.tail = to_abs(vga::WIDTH / 2 - INITIAL_LENGTH + 1, vga::HEIGHT / 2);
    342.  
    343.             vga::clear();
    344.             vga::fill(self.tail, INITIAL_LENGTH, vga::Cell::new(SNAKE_COLOR, INITIAL_DIRECTION.as_raw()));
    345.  
    346.             Self::spawn_food();
    347.         }
    348.  
    349.         pub fn step(&mut self) -> bool {
    350.             let head_direction = vga::cell(self.head).sym();
    351.             self.head = self.head.wrapping_add_signed(head_direction as isize);
    352.             if self.head >= (vga::WIDTH * vga::HEIGHT) { // Handles negatives as well
    353.                 return false;
    354.             }
    355.  
    356.             let new_pos_cell = vga::swap(self.head, vga::Cell::new(SNAKE_COLOR, head_direction));
    357.             if new_pos_cell.sym() == CellContent::Food.as_raw() {
    358.                 Self::spawn_food();
    359.                 return true;
    360.             } else if new_pos_cell.sym() != CellContent::None.as_raw() {
    361.                 return false;
    362.             }
    363.      
    364.             self.tail = self.tail.wrapping_add_signed(vga::swap(self.tail, vga::Cell::new(BACKGROUND_COLOR, CellContent::None.as_raw())).sym() as isize);
    365.  
    366.             return true;
    367.         }
    368.  
    369.         #[inline(never)]
    370.         pub fn set_direction(&self, direction: CellContent) {
    371.             if vga::cell(self.head).sym() != -direction.as_raw() {
    372.                 vga::set(self.head, vga::Cell::new(SNAKE_COLOR, direction.as_raw()));
    373.             }
    374.         }
    375.     }
    376. }
    377.  
    378.  
    379. #[no_mangle]
    380. extern "stdcall" fn main() -> ! {
    381.     let mut snake = snake::Snake::new();
    382.     loop {
    383.         snake.init();
    384.  
    385.         // Movement loop:
    386.         loop {
    387.             match input::read() & 0b0111_1111 {
    388.                 input::SCAN_LEFT => snake.set_direction(snake::CellContent::Left),
    389.                 input::SCAN_RIGHT => snake.set_direction(snake::CellContent::Right),
    390.                 input::SCAN_UP => snake.set_direction(snake::CellContent::Up),
    391.                 input::SCAN_DOWN => snake.set_direction(snake::CellContent::Down),
    392.                 _ => ()
    393.             }
    394.  
    395.             if !snake.step() {
    396.                 break;
    397.             }
    398.  
    399.             for _ in 0..180000 {
    400.                 unsafe { core::arch::x86::_mm_pause() };
    401.             }
    402.         }
    403.     }
    404. }
    405.  
    406. #[panic_handler]
    407. fn panic(_info: &core::panic::PanicInfo) -> ! {
    408.     loop {}
    409. }
    410.  
    Собираем и смотрим размер: мы уместились в 485 байт.
    upload_2024-1-26_23-7-52.png

    Запускаем и получаем удовольствие: мы снова великолепны!

    Snake.gif
    --- Сообщение объединено, 27 янв 2024 ---
    ...Ну и конечно, как же без факапов. На картинке с таблицей прерываний по ошибке написал GDTR. Разумеется, там должен быть IDTR (Interrupt Descriptor Table Register).
    Акелла промахнулся ¯\_(ツ)_/¯
     
    Последнее редактирование: 26 янв 2024
    MaKsIm, Mikl___ и Thetrik нравится это.
  6. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Во имя классов, эксепшенов и рекурсивных темплейтов. Сегфолт!
    Снимок экрана_2024-01-27_12-54-04.png
     
  7. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    603
    HoShiMin, заморочено очень.
    Если по простому, змейка, и чтобы на Win32 или Win64, то просто консоль, вот набор нужных функций.
    Код (C++):
    1. HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
    2. SetConsoleActiveScreenBuffer(hConsole);
    3. ...
    4. // Вывод в экранный буфер
    5. const COORD crd = { 0,0 }; //это для старого стандарта
    6. WriteConsoleOutputCharacterW(hConsole, pScreen, nScreenWidth * nScreenHeight, crd, &dwBytesWritten);
    Ещё саму консоль надо настроить на нужный размер.
     
  8. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Тут смысл в том, чтобы всякие Убивцы не бредили, что Ржавый не годен для всяких рилтаймов, бареметалов, эмбеддадов и тд. Ты бы ему еще Юнити или Годот предложил для змейки использовать). Тоже самое технически можно было бы сделать и на Цэ, Плюсах, Дэ, Ниме, Зиге или (прости хоспаде) на Свободных Ваське и Паскале, но выбор Ржавого был осознанный. Вендовые консоли тут не при делах совсем.
     
  9. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.348
    Ну так оказалось же, что и не годен же. Как выяснилось, раст - не более чем говнофронтенд со всратым синтаксисом для ллвм, и все чего не может ллвм, не может и раст. А стотья превратилась в "как черезжопно, накачав с гитхабов джва гигабайта говнотулчейнов, собрать одностраничный сорец на ассемблере".
     
  10. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Утверждая это, соглашаешься ли ты с тем, что си и плюсы - тоже говнофронтенд со всратым синтаксисом для ллвм/gcc, и всё, чего не могут ллвм/gcc, не могут и си с плюсами?
     
  11. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    197
    Нет, может раст где-то и хорош (всёж не глупые люди писали его), но явно под натив не заточен. Почему-бы сразу не использовать асм, если всё-равно приходится делать вставки, и заворачивать их в кучу доп.тулчейнов? А переход в 32-бит, чтобы победить 16 вообще порадовал. Хотя об этом заранее было-же сказано, что:
    Инструмент справляется с задачей? -Да. Нужно его использовать в этом контексте? -Ни в коем случае.
    А про вендовую консоль удачная получилась шутка. :good3:
     
  12. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Утверждая это, согласится ли он с тем, что ассемблер - это всего лишь говнофронтенд со всратым синтаксисом для опкодов процессора, и все, чего не может процессор, не может ни один ассемблер?
     
  13. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Но мы должны понимать, что процессор - это лишь говнофронтенд для транзисторов, и всё, чего не могут транзисторы - не может ни один процессор.

    На асме не интересно: на гитхабе полно игр на ассемблере, помещающихся в 512 байт. А вот сделать подобное на чём-то высокоуровневом с очень жёсткими рамками по размеру - интересный опыт.
    Есть ещё хороший вариант на C++17, который остаётся в x16, не переключаясь в x32: https://github.com/adam10603/mbr_snake
    Смысл не в этом: если бы мы взяли для этого челленджа любой другой язык, мы бы пришли к тому же самому: логика на языке, но вывод на экран или вызов функций из BIOS'а всё равно был бы на ассемблерных вставках.
    И плюс, ни один язык не умеет сегментную модель (условно, мы не можем ему сказать "используй такой-то селектор при разыменовывании этого указателя").
    Разумеется, x16 и даже x32 - уже никому не нужный легаси, но показательно, что писать на расте даже под них оказалось не сложнее, чем на си. И даже в бутсекторе ещё немножко места осталось :3
     
  14. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Справедливости ради на некоторых ассемблерах (а точнее компиляторах ассемблера) можно не только программы для процессора писать.
     
  15. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Хм, но транзисторы - это просто говнофронтенд над физикой...
     
  16. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.348
    Говнокланги ваши? Разумеется, да. Но я-то ими не пользуюсь и мои элитные сишные кодесы собераются любым сишным компилем. Вы вообще помните еще, какого это писать кодесы, у которых в требованиях не стоит "собирать исключительно gcc v125.314.93849 nightly build, patched by vasya at first moon phase"?
    --- Сообщение объединено, 27 янв 2024 ---
    Какой опыт? Ты ж скопипастил просто кодес с гитхаба. Хоть бы копирайт перебил, Дениска :)
     
  17. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Хм, это интересно. А откуда?
    --- Сообщение объединено, 27 янв 2024 ---
    Забавно, что приходится это подчёркивать и даже гордиться этим в контексте сишной экосистемы.
    Кажется, в 2024м больше ни один язык этим не страдает, но ладно)
     
  18. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.348
    А, я понял. Это другое.
     
  19. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Я про это и говорю, проблемы сборок остались только в сишной экосистеме. Плюсы не исключение.
    Но вот мы выходим из пещеры, а ни в одном другом языке таких проблем нет, а во многих даже никогда и не было.
    Мы несколько страниц назад смотрели, как что собирается: почти везде вся сборка - это выполнить "%сборканейм% build", типа go build или gradle build. Независимо от версии компилятора и операционки. А многие из них ещё и сами скачают зависимости.
    На этом фоне гордиться тем, что твой код собирается везде - ну... это как гордиться тем, что он вообще собирается. Это же естественно, так и должно быть, разве нет?

    Но всё-таки, мне очень интересно, у кого я скопипастил змеюку. Сейчас мы выведем на чистую воду этого вашего Хошимина.
     
  20. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.330
    Почувствовал себя браузером Опера?
     
Статус темы:
Закрыта.