"8-pinski" - EIGHT(!) bytes intro for MSDOS
MS-DOS용 8바이트 인트로 ‘8-pinski’
Héll Mood가 MS-DOS용 8바이트 데모를 공개했습니다. 8086 명령어의 겹치는 해석과 텍스트 비디오 메모리, PC 스피커를 이용해 화면 효과와 소리를 만듭니다.
- 주제
AI 요약
Héll Mood의 ‘8-pinski’는 MS-DOS에서 실행되는 8바이트짜리 인트로입니다. 설명에 따르면 베를린 Deadline Demoparty의 256바이트 대회에서 1위를 차지했으며, 코드는 lds si,[bx+si], add al,0xad, out 61h,al, jmp short X+1로 구성됩니다. Reddit 댓글에서는 명령어가 겹쳐 실행되는 방식과 비디오 메모리를 읽고 써 화면을 만드는 과정이 분석됐습니다.
8바이트 코드의 실행 방식
코드는 COM 파일로 실행되며, DOS는 이를 코드 세그먼트 오프셋 0100h에 적재합니다. 댓글 작성자의 설명에 따르면 일반적인 DOS 환경에서 초기 레지스터 값은 AX=0000h, BX=0000h, SI=0100h입니다. 첫 명령 lds si,[bx+si]는 프로그램의 첫 네 바이트를 읽어 SI와 DS에 넣습니다. 그 결과 SI는 30C5h, DS는 AD04h가 됩니다. 다음 명령 add al,0ADh는 AL을 ADh로 바꿉니다.
마지막 상대 점프는 첫 명령의 중간인 오프셋 0101h로 이동합니다. 그 지점부터 바이트를 다시 해석하면 xor [si],al, lodsw, out 61h,al, jmp short 순으로 실행됩니다. lodsw는 DS:SI가 가리키는 2바이트를 AX로 읽고 SI를 2 증가시킵니다. 댓글 분석에 따르면 이 루프는 비디오 메모리에서 바이트를 읽고 인접한 위치에 XOR 연산을 반복합니다. 텍스트 화면 영역에서는 문자 속성 바이트를 건너뛰며 색을 만들고, 포트 61h에 값을 출력해 PC 스피커도 구동합니다. 반복 범위는 텍스트 화면에만 한정되지 않고 데이터 세그먼트 전체에 걸칩니다.
Reddit 반응
- @u/vytah — 코드는 다음과 같습니다.
X: lds si, word ptr [bx+si] add al, 0xad out 61h, al jmp short X+1. 바이너리 코드는C5 30 04 AD E6 61 EB F9이며 코드 세그먼트의 0100h에 적재됩니다. COM 파일은 새 코드 세그먼트의 0100h에 로드됩니다. 그 앞의 256바이트는 관련 없는 DOS API 용도로 쓰입니다. CP/M과의 호환성을 위해 데이터 세그먼트는 코드 세그먼트와 같게 설정됩니다. 대부분의 DOS 버전에서 CPU 레지스터 초기값은 AX=0000h, BX=0000h, SI=0100h이며 CS=DS입니다. 따라서 첫 두 명령 뒤에는 AX=00ADh, SI=30C5h, DS=AD04h가 됩니다.lds는 프로그램의 첫 4바이트를 읽어 각각 SI와 DS 레지스터에 넣습니다. 이후 데이터 세그먼트, 즉 대부분의 메모리 읽기·쓰기 명령이 사용하는 영역은 AD040h에서 시작합니다. 이 주소는 EGA/VGA 비디오 메모리 안에 있습니다. 세그먼트 범위는 AD040h부터 BD03Fh까지이며, B8000h에서 시작하는 텍스트 모드 영역과 겹칩니다. 텍스트 모드 버퍼의 2,000개 항목은 각각 문자와 속성을 담는 2바이트 값이며 전체 크기는 4,000바이트입니다. 짝수 바이트는 문자, 홀수 바이트는 속성입니다. 마지막 짧은 점프가 명령어 경계에서 벗어나므로 첫 점프 뒤에는Y: xor byte ptr [si], al; lodsw ax, word ptr [si]; out 0x61, al; jmp short Y가 실행됩니다. XOR 명령은 SI가 가리키는 바이트를 토글합니다.lodsw는 SI가 가리키는 2바이트를 AX로 읽은 뒤 SI를 2 증가시키므로 SI는 계속 홀수로 유지됩니다. 결국 비디오 메모리에서 한 바이트를 읽고 두 주소 뒤의 바이트에 XOR 연산을 반복합니다. 이런 과정은 종종 셀룰러 오토마톤 같은 동작을 만듭니다. 루프는 텍스트 화면뿐 아니라 64KB 세그먼트 전체를 돕니다. 텍스트 영역에서는 속성 바이트를 건너뛰며 색을 만듭니다. 마지막으로out 61h, al은 여러 장치와 연결된 I/O 포트 61h에 계속 값을 씁니다. 연결 방식은 PC 종류마다 다르지만, 이 데모에서는 내장 PC 스피커를 구동합니다.- @u/cybermind — 어젯밤 NASM으로 이 코드를 조립해 DOSBox에서 실행한 다음 역어셈블했습니다. 점프가 첫 명령어의 중간을 가리키는 걸 보고 마지막 7바이트를 반복해 다음 명령어열을 확인했습니다. 그 뒤 잠을 자는 대신 몇 시간 동안 누워서 이 동작을 따라가며 opcode 문서 등을 찾아봤습니다. SI가 텍스트 버퍼의 서로 다른 위치에 쓰도록 어떻게 움직이는지는 알아내지 못했습니다. 찾아본 문서에는
lodsw가 SI를 증가시킨다는 설명이 없었는데, 이제 이해했습니다. IDA 역어셈블 결과와 제 주석을 바탕으로 작성한 코드는 다음과 같습니다.seg000:0100 loc_0: ; CODE XREF: seg000:0106↓j,seg000:0100 C5 30 lds si, [bx+si] ; [bx+si]의 16비트 워드(30C5h)를 SI에, 다음 16비트 워드를 DS에 로드(AD04h),seg000:0102 04 AD add al, 0ADh,seg000:0104 E6 61 out 61h, al ; AL을 스피커로 출력,seg000:0106 EB F9 jmp short near ptr loc_0+1. 0101h에서 시작하는 다른 해석은 다음과 같습니다.seg000:0101 loc_1: ; CODE XREF: seg000:0106↓j,seg000:0101 30 04 xor [si], al,seg000:0103 AD lodsw ; [ds:si]의 16비트 워드를 AX에 로드하고 SI를 2 증가,seg000:0104 E6 61 out 61h, al ; AL을 스피커로 출력,seg000:0106 EB F9 jmp short loc_1. A000h 세그먼트는 VGA 버퍼이고 B000h 세그먼트는 텍스트 버퍼입니다.
- @u/cybermind — 어젯밤 NASM으로 이 코드를 조립해 DOSBox에서 실행한 다음 역어셈블했습니다. 점프가 첫 명령어의 중간을 가리키는 걸 보고 마지막 7바이트를 반복해 다음 명령어열을 확인했습니다. 그 뒤 잠을 자는 대신 몇 시간 동안 누워서 이 동작을 따라가며 opcode 문서 등을 찾아봤습니다. SI가 텍스트 버퍼의 서로 다른 위치에 쓰도록 어떻게 움직이는지는 알아내지 못했습니다. 찾아본 문서에는
- @u/JuanPabloVassermiler — 재미있는 사실: 이름은 이 인트로에 등장하는 기본 프랙털인 시에르핀스키 삼각형(Sierpiński triangle)에서 따왔습니다.
- @u/abraxasnl — 소리까지 나오네요?
- @u/Ysilla — 제작자의 이전 16바이트 데모 ‘Wake Up’은 음악이 훨씬 더 좋습니다.
- @u/abraxasnl — 정말 끝내주는 곡입니다!
- @u/ShinyHappyREM — https://www.pouet.net/prod.php?which=107055
- @u/Celos — 닫아서 도킹한 노트북 위에 눕는 걸 좋아하는 우리 고양이가 이제 몹시 불안해합니다.
- @u/stgiga — 기발하네요. 저는 3,045바이트짜리 JS 데모를 만든 적이 있습니다. DOS 코드 8바이트는 저도 못 만들겠지만, 데모신에 재능 있는 사람이 많고 작업도 어렵다는 점은 경험으로 압니다.
원문: Reddit / 번역·요약: Trawling