80386 마이크로코드 역어셈블됨
(reenigne.org)- 80386 마이크로코드 ROM은 94,720비트로 8086의 10,752비트보다 훨씬 커서 이미지 변환과 검증이 까다로웠음
- 고해상도 다이 이미지에서 이미지 처리·신경망·사람 보조 자동화를 조합해 며칠 만에 바이너리 블롭을 추출하고 교차 검증함
- 역어셈블 과정에서 μ-op 배열, 비트 필드, 명령 종료 패턴, 명령 디코더와 보호 테스트 PLA 구조가 점진적으로 드러남
- 80386은 모든 명령에 대응하는 마이크로코드가 있으며, 많은 루틴은 알고리듬보다 곱셈·나눗셈 하드웨어와 배럴 시프터 설정을 담당함
- 보호 모드의 IO 권한 비트맵 처리에서 4바이트 포트 접근 시 처음 3개 주소만 검사하는 듯한 잠재 결함이 발견됐지만 아직 확정되지는 않음
80386 마이크로코드 추출과 역어셈블
- 8086 마이크로코드 역어셈블 이후 Ken Shirriff가 80386의 마이크로코드 ROM 고해상도 이미지를 제공했지만, 80386 ROM은 94,720비트로 8086의 10,752비트보다 훨씬 커 변환과 검증이 훨씬 어려웠음
- 8086에는 전체 구조와 일부 코드 조각을 담은 특허가 있어 검색 단서가 있었지만, 80386은 완전한 블랙박스에 가까워 큰 바이너리 덩어리에서 구조를 찾기 어려웠음
- Discord에서 GloriousCow와 Smartest Blob 등이 80386 다이의 고해상도 이미지에서 마이크로코드를 추출하는 작업을 이어갔고, 이미지→바이너리→이해 가능한 마이크로코드로 바꾸는 과정이 핵심 난관이었음
- 이미지 처리, 신경망, 사람 보조 자동화를 조합해 며칠 만에 이미지에서 바이너리 블롭을 추출하고 교차 검증함
역어셈블 과정에서 드러난 구조
- 바이너리 추출 뒤에도 역어셈블은 쉽지 않았고, 여러 패턴을 맞춰 가며 한 축에는 μ-op, 다른 축에는 μ-op 비트가 오도록 재배열하는 방식을 파악함
- 사용되지 않은 μ-op 블록이 한쪽 끝에 있어 μ-op을 읽는 순서를 파악하는 단서가 됨
- μ-op 비트를 여러 필드로 나누는 과정에서는 8086 마이크로코드 작업 경험을 바탕으로 소스 레지스터와 목적지 레지스터 필드를 찾아감
- 80386은 ALU 연산을 2사이클에 수행할 수 있으므로, 첫 번째 사이클에서 두 피연산자를 ALU에 적재하고 두 번째 사이클에서 결과를 목적지로 보내려면 ALU의 두 번째 입력을 지정하는 필드가 필요했음
- 반복적으로 나타나는 패턴은 명령의 끝을 나타내는 것으로 추정됐고, 이후 실제로 맞는 것으로 확인됨
- Ken은 80386 다이의 여러 선과 논리 비트를 추적해 내부 연결 방식을 파악하는 데 기여했고, 새로 밝혀진 구조는 같은 구성물을 쓰는 다른 마이크로코드 조각을 해석하는 단서가 됨
- 마이크로코드와 함께 여러 작은 PLA로 구성된 명령 디코더와 보호 테스트 PLA도 해독되면서 386 명령을 마이크로코드 조각과 연결할 수 있게 됨
8086과 다른 80386의 실행 방식
- 80386은 대부분의 명령에서 8086보다 사이클당 훨씬 빠르며, 이를 위해 더 많은 트랜지스터를 사용했음
- 8086에서 마이크로코드로 구현되던 여러 알고리듬은 80386에서 사실상 하드웨어 가속기가 맡음
- 80386 마이크로코드의 상당 부분은 알고리듬 자체보다 곱셈·나눗셈 하드웨어, 배럴 시프터, 보호 테스트 유닛 같은 가속기를 설정하는 역할을 함
- 역어셈블 작업의 큰 비중은 이러한 가속기 인터페이스와 마이크로코드 사이의 연결 방식을 파악하는 데 있었음
마이크로코드 엔트리와 명령 처리
- 디코딩 ROM에서 진입하는 마이크로코드 엔트리 포인트는 215개로, 8086의 60개보다 크게 늘었음
- 엔트리 증가는 새 명령 추가뿐 아니라 피연산자가 레지스터인지 메모리인지, CPU가 실모드인지 보호 모드인지, REP 접두사가 사용 중인지에 따라 같은 명령도 서로 다른 루틴으로 처리되기 때문임
- 모든 엔트리 목록은
fields.txt파일에 있으며, 서브루틴과 공유 코드도 함께 들어 있음 - 많은 상위 마이크로코드 루틴은 적은 작업만 수행한 뒤 다른 엔트리 포인트와 공유하는 루틴으로 점프하므로, 상위 루틴의 크기만으로는 의미를 파악하기 어려움
- 명령 디코더가 사용할 루틴을 정할 때 opcode만 쓰지 않기 때문에, 각 엔트리 포인트가 처리하는 opcode 개수만으로도 구조를 설명하기 어려움
모든 명령은 마이크로코드로 처리됨
- 80386은 8086이나 현대 CPU와 달리 항상 μ-op을 실행하며, 모든 명령에 대응하는 마이크로코드가 있음
- 마이크로코드로 처리되지 않는 명령은 없는 것으로 확인됨
사용되지 않는 코드와 예외 처리 흔적
0x849부터0x856까지의 루틴은 마이크로코드 역어셈블에서unused?로 표시되어 있으며, 연결된 엔트리 포인트가 없는 것으로 보임- 이 루틴의 정확한 동작은 완전히 확실하지 않지만,
0x8e9부터0x8f5까지의#PF(PAGE_FAULT) 루틴과 공통점이 많음 - 두 루틴 모두 paging unit의 마지막 오류 코드를 설정한 뒤 interrupt 0x0e로 이어짐
- 차이는 이 루틴이 fault linear address 대신 paging unit에서 나온 알 수 없는 값을
CR2에 설정한다는 점임 - 나머지 마이크로코드는 CPU의 문서화된 동작을 구현하도록 설계된 것으로 보이며, 저수준 디버깅용 ICE(In-Circuit Emulator) 하드웨어와 상호작용하는 루틴은 문서화되지 않은 동작에 해당함
숨겨진 기능과 가능한 IO 권한 비트맵 결함
- 아직 실제 386 머신에서 시험하지 못해 확정할 수는 없지만, 보호 모드 OS 일부가 사용자 모드 프로세스에 IO 포트 접근을 제한적으로 허용할 때 쓰던 IO 권한 비트맵 처리에 결함 가능성이 있음
- 4바이트 포트 접근이 발생할 때 마이크로코드는 처음 3개 주소의 권한 비트만 검사하는 것처럼 보임
- 프로세스가 권한을 가진 IO 포트 공간의 경계에서 이런 접근을 수행하면, 마지막 바이트 접근이 잘못 성공해 OS가 사용자 접근을 의도하지 않은 하드웨어 레지스터에 닿을 가능성이 있음
- 이 버그는 매우 특이한 경우라 마이크로코드 역어셈블 없이는 놓쳤을 수 있지만, 40년 넘게 널리 쓰인 하드웨어의 보안 버그가 발견되지 않았다는 점은 이례적임
- 해당 동작은 일부 CPU 버전에만 있었을 수도 있고, 루틴 해석이 잘못되어 실제로는 올바르게 동작할 수도 있음
- 이 마이크로코드는 80386 초기 버전의 것으로 보이지 않으며,
XBTS와IBTS명령은 디코더를 제외하면 흔적이 없음
학습 자료와 다운로드 위치
- nand2mario의 80386 내부 구조 글은 역어셈블을 이해하기 위한 출발점으로 유용함
- 80386 Multiplication and Division
- 80386 Barrel shifter
- 80386 Protection
- 80386 Memory Pipeline
- 역어셈블 결과는 GitHub의 x86 microcode 저장소에서 받을 수 있음
parts.txt는 다른 파일들의 역할을 안내하며,microcode_10.txt는 마이크로코드 역어셈블 자체로 바로 들어가는 파일임
댓글과 토론
Hacker News 의견들
-
고해상도 다이 이미지에서 어떻게 마이크로코드를 복원할 수 있는지 궁금함
과정이 각 트랜지스터를 인식해서 회로를 모델링하는 건지, 결과물이 Verilog 같은 형태인지도 알고 싶음- 추출 과정에 조금 참여했는데, 먼저 마이크로코드 배열의 행과 열이 만나는 위치를 기준으로 모든 비트의 x,y 좌표를 표시함
그다음 0과 1을 분류하는데, 1은 트랜지스터 존재와 폴리실리콘의 틈으로 시각적으로 구분됨
Intel 마이크로코드 특성상 0이 훨씬 많다고 가정할 수 있어서, 트랜지스터가 있으면 1로 봤음
색상 임계값으로 자동 처리하는 도구도 있지만, 모자이크 일부가 흐리고 먼지가 들어가 가짜 1비트가 많이 생겨 잘 안 맞았음
대신 합성곱 신경망으로 추출한 비트 영역을 0/1로 분류하게 학습시키고, 결과를 원본 모자이크 위에 흰색/검은색 사각형 50% 불투명도로 덮어 확인했음
이후 며칠 동안 지루하게 오류를 검수했고, 최종적으로 원시 2차원 비트 배열을 얻었으며 다음 단계는 그 배열에서 마이크로코드 워드를 추출하는 것임 - Nintendo 64 락아웃 메커니즘 칩을 층별로 벗겨내는 영상이 있고, 이런 작업을 여러 방식으로 꽤 깊게 다룸
https://youtu.be/HwEdqAb2l50?si=VFLed64PZvpCHfy1 - 이미지 자체를 보면 됨
“위 사진은 마이크로코드 ROM 일부를 보여준다. 현미경으로 보면 마이크로코드 ROM의 내용이 보이고, 각 위치에 트랜지스터가 있는지 없는지에 따라 비트를 읽어낼 수 있다”
https://www.righto.com/2020/06/a-look-at-die-of-8086-process... - 마이크로코드는 ROM에 들어 있고, 1과 0이 다르게 보이는 규칙적인 구조임
- 추출 과정에 조금 참여했는데, 먼저 마이크로코드 배열의 행과 열이 만나는 위치를 기준으로 모든 비트의 x,y 좌표를 표시함
-
관련 진행 중 스레드: z386: An Open-Source 80386 Built Around Original Microcode - https://news.ycombinator.com/item?id=48248014 - 2026년 5월, 댓글 22개
-
며칠 전 reenigne 블로그를 확인했을 때는 “음, 2020년 이후 글이 없네” 싶었는데, 다시 올라와서 반가움
블로그가 33년 전까지 거슬러 올라가는 것도 특히 재미있음- 조회수 카운터가 올라간 게 글을 쓰는 계기가 됐을지도 모름
-
마이크로프로그래밍을 기초부터 설명하는 좋은 책: https://www.amazon.com/Computation-Structures-Optical-Electr...
무료 PDF도 쉽게 찾을 수 있음 -
이 마이크로코드를 리버스 엔지니어링하는 데 필요한 노력이 인상적이고, 386 아키텍처를 깊게 파고든 훌륭한 글임
-
실제 마이크로코드 구현을 보면 오래된 프로세서가 복잡한 연산을 어떻게 처리했는지 덜 신비롭게 느껴짐
-
386은 22년 생산 기간 동안 작은 변경이 많았기 때문에, 이 코드가 어느 386 리비전에서 나온 것인지 아는 게 중요함
- 한 가지 단서는 리셋 시 EDX에 로드되는 값임
9B5 BIST1 -> TMPD 0x0303 PASS2
9B6 SIGMA -> EDX
9B7 BIST2 -> TMPE TMPD XOR
9B8 SIGMA 0x3ddc0c2c XOR
9B9 SIGMA -> EAX BOOTUP_JUMP JFPUOK
0x303은 제품군 3, 모델 0, 스테핑 ID 3을 뜻함
- 한 가지 단서는 리셋 시 EDX에 로드되는 값임
-
이걸 해독하는 데 필요한 블랙박스 분석은 엄청 어렵지만, 성공하면 굉장히 재미있고 보람도 클 것 같음
-
이런 글을 이해하려고 대학에서 어려운 과목을 들었던 게 만족스럽고, 2015년 당시 HN이 그런 사고를 자극해준 것도 좋았음
지금은 저수준 프로그래밍 지식을 크게 쓰지 않더라도, 이런 글을 읽을 때마다 의식이 풍부해지는 느낌이 들어 멋짐
대학에 접근하기 어려운 사람에게는 nand2tetris.org를 추천함- 게이트부터 직접 마이크로프로세서를 만들어보는 것이 마이크로코드 설계와 프로세서 동작을 이해하는 더 쉬운 방법임
단순한 예전 설계인 RISC나 Transputer를 공부해도 도움이 되고, 80386은 그 스펙트럼의 반대편에 있음
오래된 나쁜 설계와 하위 호환을 유지하려다 불필요하게 복잡해졌기 때문임
칩 설계를 배우는 데 꼭 대학이 필요한 것은 아니고, Alan Kay 강연을 몇 개 보거나 Bitsavers의 컴퓨터 설계를 훑어보는 것도 좋은 출발점임
FPGA보다 쉬운 방식으로 게이트 수준 설계를 시뮬레이션하고 2026년 기준 200달러 미만으로 칩 위 트랜지스터로 변환하는 Morphle Logic을 만들었음
결국 더 커지고 빨라지며 저렴한 웨이퍼 규모 슈퍼컴퓨터 통합으로 이어질 수 있음
https://github.com/fiberhood/MorphleLogic/blob/main/README_M...
https://www.youtube.com/watch?v=vbqKClBwFwI
https://www.youtube.com/watch?v=f1605Zmwek8
http://bitsavers.informatik.uni-stuttgart.de/pdf/xerox/alto/... - nand2tetris를 몇 번 해봤지만, 모든 추상화 수준에서 단순함을 강조하기 때문에 마이크로코드 같은 것은 건너뜀
그 단순함 자체는 훌륭한 교훈이고 많은 영감을 줬지만, 1990년대 대학에서 들은 전기공학 수업은 nand2tetris와 비슷하게 8086류 CPU가 어떻게 만들어지는지 다루면서도 마이크로코드 동작을 설명했음
내부 프로그램 카운터가 제어 워드 테이블을 따라가고, 각 비트가 CPU의 제어 가능한 부분을 직접 조율하는 식이었음
각자 시뮬레이터에서 명령 하나를 구현했는데, 나는 DEC, 즉 감소 명령을 맡았음
어떤 의미에서는 nand2tetris의 명령어가 마이크로코드라고 볼 수도 있음
명령어 비트가 하드웨어를 직접 제어하고 첫 비트가 두 종류의 명령을 고르므로, 명령 하나당 코드 단계가 1개뿐임
반면 마이크로코드는 명령 하나가 임의 개수의 마이크로코드 단계를 가질 수 있음
Ben Eater의 브레드보드 8비트 CPU 영상에서는 명령어의 4비트 연산 코드와 단계 카운터로 ROM을 인덱싱해 제어 워드를 정함
이 ROM은 충분히 복잡한 논리 게이트로도 만들 수 있는 부분을 대신하며, 전자회로를 직접 만지고 문제를 해결해야 하므로 하드웨어 쪽 다음 단계로 좋음
다만 RAM이 16바이트뿐이라 nand2tetris처럼 더 높은 추상화 계층을 만들기 어렵다는 점은 아쉬움
그 시점에서는 더 나은 설계로 다시 만들거나 PCB에 올리거나, 6502 프로젝트로 넘어가 타이머, CPU, ROM, RAM, 입출력, UART 등을 하나로 묶어 생각한 뒤 이미 그런 것들이 합쳐진 마이크로컨트롤러로 넘어갈 수 있음
논리 게이트로 CPU를 만드는 방법을 읽고 싶다면 Charles Petzold의 Code는 느리게 설명하고 최근 개정됐으며, Danny Hillis의 Pattern on the Stone은 더 빠르게 진행됨
Code 2판은 4비트 사이클 카운터와 하드와이어드 논리 게이트로 각 사이클 동작을 정하고, 일부 논리에 다이오드 배열을 쓰는데 이것도 마이크로코드로 봐야 하는지 궁금함 - nand2tetris가 마이크로코드를 다루거나 사용하는지 궁금함
- 게이트부터 직접 마이크로프로세서를 만들어보는 것이 마이크로코드 설계와 프로세서 동작을 이해하는 더 쉬운 방법임