Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

SIC/XE 2-Pass Assembler 구현

시스템 프로그래밍 과목 (2024-1) | C언어 구현 | SIC/XE 아키텍처


프로젝트 개요

이 프로젝트는 교육용 가상 CPU 아키텍처인 SIC/XE를 위한 2-Pass 어셈블러(Assembler) 를 C언어로 직접 구현한 시스템 소프트웨어입니다.

어셈블러는 사람이 읽을 수 있는 어셈블리 언어(Assembly Language)를 CPU가 직접 실행할 수 있는 기계어(Object Code)로 변환해주는 시스템 소프트웨어입니다. 컴파일러나 인터프리터처럼 프로그래밍 언어 처리기의 가장 하위 계층에 해당하며, 하드웨어와 소프트웨어의 경계에서 동작합니다.


배경 지식

SIC/XE란?

SIC(Simplified Instructional Computer) XE(Extra Equipment) 는 Leland Beck의 교재 System Software: An Introduction to Systems Programming에서 소개된 교육 전용 가상 컴퓨터 아키텍처입니다. 실제 CPU(x86, ARM 등)처럼 레지스터, 명령어 형식, 주소지정 방식을 갖추고 있어, 실제 시스템 소프트웨어의 동작 원리를 학습하기에 적합합니다.

주요 레지스터:

레지스터 번호 역할
A (Accumulator) 0 산술/논리 연산의 기본 레지스터
X (Index) 1 인덱스 주소지정에 사용
L (Linkage) 2 서브루틴 복귀 주소 저장
B (Base) 3 Base 상대 주소지정에 사용
S, T 4, 5 범용 레지스터
PC (Program Counter) 8 다음 실행할 명령어의 주소
SW (Status Word) 9 조건 코드 저장

명령어 형식(Instruction Format):

SIC/XE는 명령어 길이에 따라 4가지 형식을 가집니다.

Format 1 (1바이트): [ opcode(8) ]
Format 2 (2바이트): [ opcode(8) | r1(4) | r2(4) ]
Format 3 (3바이트): [ opcode(6) | n | i | x | b | p | e | disp(12) ]
Format 4 (4바이트): [ opcode(6) | n | i | x | b | p | e | address(20) ]

nixbpe 플래그 (Format 3/4의 핵심):

비트 이름 역할
n indirect 간접 주소지정 (오퍼랜드가 주소의 주소)
i immediate 즉시 주소지정 (오퍼랜드가 상수값)
x index 인덱스 레지스터 X를 주소에 더함
b base Base 레지스터 기준 상대 주소지정
p pc-relative PC 기준 상대 주소지정
e extended Format 4 (4바이트 확장)

2-Pass 어셈블러란?

어셈블러가 소스코드를 한 번만 읽으면 문제가 생깁니다. 예를 들어, 아직 정의되지 않은 레이블(심볼)을 참조하는 명령어가 있을 때 주소를 알 수 없습니다. 이를 전방 참조(Forward Reference) 문제라 합니다.

2-Pass 방식은 소스코드를 두 번 스캔하여 이 문제를 해결합니다:

[소스코드] → [Pass 1] → [심볼 테이블 / 리터럴 테이블] → [Pass 2] → [오브젝트 코드]
  • Pass 1: 소스를 처음부터 끝까지 읽으며 모든 레이블의 메모리 주소를 계산하여 심볼 테이블을 구축합니다.
  • Pass 2: 심볼 테이블을 참조하여 각 명령어를 실제 기계어 코드로 변환합니다.

구현 범위

과제 구현 내용
과제 1 Pass 1 (토큰 파싱) + OPCODE 매핑 출력
과제 2 Pass 1 (심볼/리터럴 테이블 + LOCCTR) + Pass 2 (오브젝트 코드 전체 생성)

과제 1 — 토큰 파서 및 OPCODE 매핑

목적

SIC/XE 어셈블리 소스코드의 각 줄을 파싱하여 라벨(label), 연산자(operator), 피연산자(operand), 주석(comment) 으로 분리하고, 각 연산자에 해당하는 OPCODE(기계어 번호) 를 찾아 출력합니다.

주요 구현 함수

함수 역할
init_inst_table() inst_table.txt에서 SIC/XE 명령어 목록을 읽어 테이블 구축
init_input() 어셈블리 소스파일(input.txt)을 줄 단위로 읽기
token_parsing() 한 줄의 소스코드를 탭(tab) 구분자 기준으로 토큰 분리
search_opcode() 연산자 문자열로 inst_table에서 OPCODE 검색
assem_pass1() 전체 소스에 대해 token_parsing 반복 실행
make_opcode_output() 토큰 + OPCODE를 파일로 출력

동작 흐름

inst_table.txt 읽기
       ↓
inst_table[] 구축 (명령어명, OPCODE, 형식, 오퍼랜드 수)
       ↓
input.txt 읽기
       ↓
각 줄을 token_parsing()으로 파싱 → tokens[] 구축
       ↓
각 token의 operator로 search_opcode() 호출
       ↓
output_20203099.txt에 [레이블 | 연산자 | 피연산자 | OPCODE] 출력

토큰 파싱 규칙

소스코드는 탭 문자(\t)를 구분자로 사용합니다:

[레이블]\t[연산자]\t[피연산자]\t[주석]
  • 줄 첫 글자가 .이면 전체 주석 처리
  • 줄 첫 글자가 \t이면 레이블 없음
  • 피연산자는 ,로 구분하여 최대 3개까지 파싱

입/출력 예시

입력 (input.txt):

COPY	START	0			COPY FILE FROM IN TO OUTPUT
FIRST	STL	RETADR			SAVE RETURN ADDRESS
CLOOP	+JSUB		RDREC	READ INPUT RECORD
	LDA	LENGTH			TEST FOR EOF
	COMP	#0

출력 (output_20203099.txt):

COPY	START	0	
FIRST	STL	RETADR	14
CLOOP	+JSUB	RDREC	48
	LDA	LENGTH	00
	COMP	#0	28

각 행 마지막 컬럼이 해당 명령어의 16진수 OPCODE입니다. +JSUB처럼 + 접두사(Format 4)가 붙은 경우에도 접두사를 제거하고 JSUB로 검색합니다.


과제 2 — 완전한 2-Pass 어셈블러 (오브젝트 코드 생성)

목적

과제 1의 Pass 1을 확장하여 심볼 테이블, 리터럴 테이블, 위치 계산기(LOCCTR) 를 완성하고, Pass 2에서 SIC/XE 표준 오브젝트 코드를 생성합니다. 생성된 오브젝트 코드는 링커(Linker)와 로더(Loader)가 프로그램을 메모리에 올리는 데 사용됩니다.

핵심 자료구조

/* 명령어 하나의 정보 */
typedef struct _inst {
    char str[10];       // 명령어 이름 (예: "LDA", "JSUB")
    unsigned char op;   // OPCODE 바이트 (예: 0x00, 0x48)
    int format;         // 명령어 형식 (1, 2, 3)
    int ops;            // 오퍼랜드 개수
} inst;

/* 소스코드 한 줄을 분해한 토큰 */
typedef struct _token {
    char *label;                          // 레이블
    char *operator;                       // 연산자
    char *operand[MAX_OPERAND_PER_INST];  // 피연산자 (최대 3개)
    char *comment;                        // 주석
    char nixbpe;                          // 주소지정 비트 플래그
} token;

/* 심볼 테이블의 항목 */
typedef struct _symbol {
    char name[10];    // 심볼(레이블) 이름
    int addr;         // 메모리 주소
    char section[10]; // 소속 제어 섹션
} symbol;

/* 리터럴 테이블의 항목 */
typedef struct _literal {
    char literal[20]; // 리터럴 표현식 (예: "=C'EOF'", "=X'05'")
    int addr;         // 메모리 주소
} literal;

Pass 1 — 심볼 테이블 및 리터럴 테이블 구축

LOCCTR(Location Counter): 현재 처리 중인 명령어가 메모리의 어느 주소에 위치할지 추적하는 변수입니다.

처리하는 어셈블러 지시어:

지시어 역할 LOCCTR 변화
START 프로그램 시작, 초기 주소 설정 초기값으로 설정
CSECT 새 제어 섹션 시작 0으로 리셋
RESW n n 워드(3n 바이트) 메모리 예약 +3n
RESB n n 바이트 메모리 예약 +n
WORD 3바이트 정수 상수 +3
BYTE 1바이트 이상 상수 (C'', X'') +문자열 길이
EQU 심볼에 표현식 값 할당 변화 없음
LTORG 현재 위치에 리터럴 풀 배치 +리터럴 크기 합
EXTDEF 외부에 공개할 심볼 선언 변화 없음
EXTREF 외부에서 가져올 심볼 선언 변화 없음

EQU 처리 (심볼 간 차이 계산):

// EQU BUFEND-BUFFER 처리 예시
// 두 심볼의 주소 차이를 계산하여 새 심볼에 할당
addr = symbol_table["BUFEND"].addr - symbol_table["BUFFER"].addr;

Pass 2 — 기계어 코드 생성

Pass 1에서 구축된 심볼/리터럴 테이블을 기반으로 각 명령어를 실제 기계어 바이트로 변환합니다.

Format별 코드 생성:

Format 1: [ opcode(8) ]
  예) FIX → C4

Format 2: [ opcode(8) | r1(4) | r2(4) ]
  예) COMPR A,S → A0 04

Format 3: [ opcode(6)|n|i | x|b|p|e | disp(12) ]
  예) LDA LENGTH → 00 (n=1,i=1,p=1) disp = LENGTH주소 - (PC+3)

Format 4: [ opcode(6)|n|i | x|b|p|e | address(20) ]
  예) +JSUB RDREC → 4B (e=1) address = 0 (링킹 시 수정)

주소지정 모드 결정 로직:

// '#' 접두사 → 즉시 주소지정 (n=0, i=1)
// '@' 접두사 → 간접 주소지정 (n=1, i=0)
// 기본          → 단순/직접 (n=1, i=1)
// ',X' 접미사  → 인덱스 주소지정 (x=1)

// PC 상대 주소지정 (3바이트 명령어 기본값)
disp = target_addr - (locctr + 3);  // -2048 ~ +2047 범위이면 p=1

오브젝트 코드 레코드 형식

생성되는 오브젝트 코드는 링커가 처리하는 표준 레코드 형식으로 출력됩니다:

레코드 형식 의미
H H섹션명 시작주소 섹션길이 Header: 섹션 기본 정보
D D심볼명 주소 ... Define: 외부에 공개하는 심볼 목록
R R심볼명 ... Reference: 외부에서 참조하는 심볼 목록
T T시작주소 길이 코드 Text: 실제 기계어 코드 (최대 30바이트/레코드)
M M주소 길이 +심볼 Modification: 링킹 시 수정이 필요한 위치
E E실행주소 End: 프로그램 진입점

수정 레코드(M Record)의 역할:
Format 4 명령어에서 외부 섹션의 심볼을 참조할 때, 링킹 전에는 주소를 알 수 없습니다. M 레코드는 링커에게 "이 주소의 값을 해당 심볼의 실제 주소로 수정하라"고 알립니다.


입/출력 파일

입력

파일 설명
inst_table.txt SIC/XE 명령어 목록 (이름, 형식, OPCODE, 오퍼랜드 수)
input.txt 어셈블할 SIC/XE 소스코드

inst_table.txt 형식:

LDA,3,00,1
JSUB,3,48,1
CLEAR,2,B4,1
FIX,1,C4,0

(명령어명, 형식(바이트수), OPCODE(16진수), 오퍼랜드 개수)

출력

과제 1:

파일 설명
output_20203099.txt 소스코드 + 각 명령어의 OPCODE

과제 2:

파일 설명
output_symtab.txt 심볼 테이블 (심볼명, 주소, 소속 섹션)
output_littab.txt 리터럴 테이블 (리터럴 표현식, 주소)
output_objectcode.txt SIC/XE 오브젝트 코드 (H/D/R/T/M/E 레코드)

출력 예시

심볼 테이블 (output_symtab.txt):

    COPY       0
    FIRST      0          +1 COPY
    CLOOP      3          +1 COPY
    BUFFER     33         +1 COPY
    BUFEND     1033       +1 COPY
    RDREC      0
    RLOOP      9          +1 RDREC

리터럴 테이블 (output_littab.txt):

    =C'EOF'              30
    =X'05'               1B

오브젝트 코드 (output_objectcode.txt):

HCOPY  000000001033
DBUFFER000033BUFEND001033LENGTH00002D
RRDREC WRREC 
T0000001D1720274B1000000320232900003320074B1000003F2FEC0320160F2016
T00001D0D0100030F200A4B1000003E2000
T00003003454F46
M00000405+RDREC
M00001105+WRREC
M00002405+WRREC
E000000
HRDREC 00000000002B
...

제어 섹션(Control Section) 및 연결 편집

이 프로젝트의 입력 파일은 세 개의 제어 섹션으로 구성된 멀티-섹션 프로그램입니다:

COPY    ─ 메인 섹션: 파일을 읽어 출력하는 루프 제어
RDREC   ─ 서브루틴: 입력 장치에서 레코드를 버퍼에 읽음
WRREC   ─ 서브루틴: 버퍼의 내용을 출력 장치에 씀

각 섹션은 독립적으로 어셈블되며, EXTDEF/EXTREF로 섹션 간 심볼을 공유합니다. 링커는 M(Modification) 레코드를 참고하여 외부 심볼 참조를 실제 주소로 연결합니다.


프로젝트 구조

시스템프로그래밍24/
├── 과제1/
│   ├── my_assembler_20203099.c    # Pass 1 + OPCODE 출력 구현
│   ├── my_assembler_20203099.h    # 자료구조 및 함수 선언
│   ├── inst_table.txt             # SIC/XE 명령어 목록
│   ├── input.txt                  # 어셈블리 소스코드 (3-섹션 프로그램)
│   └── output_20203099.txt        # 출력: OPCODE 매핑 결과
│
└── 과제2/
    ├── my_assembler_20203099-1.c  # 완전한 2-Pass 어셈블러 구현
    ├── my_assembler_20203099-1.h  # 자료구조 및 함수 선언
    ├── inst_table.txt             # SIC/XE 명령어 목록
    ├── input.txt                  # 어셈블리 소스코드 (동일 3-섹션 프로그램)
    ├── output_symtab.txt          # 출력: 심볼 테이블
    ├── output_littab.txt          # 출력: 리터럴 테이블
    └── output_objectcode.txt      # 출력: SIC/XE 오브젝트 코드

기술 스택 및 핵심 구현 포인트

  • 언어: C (표준 C99)
  • 빌드 환경: GCC / CLion (CMake)

구현 난이도 및 학습 포인트:

  1. 문자열 파싱: 탭 구분자 기반 토큰 파싱, 문자열 포인터 범위([start, end)) 기반 동적 메모리 할당
  2. 비트 연산: nixbpe 6개 비트 플래그를 비트 마스킹으로 조합하여 명령어 코드 생성
  3. PC 상대 주소 계산: disp = target - (locctr + 3) 및 범위 초과 시 Base 상대로 전환
  4. EQU 심볼 처리: 심볼 테이블을 역참조하여 표현식(BUFEND-BUFFER) 계산
  5. LTORG/END 리터럴 배치: 아직 주소가 할당되지 않은 리터럴에 현재 LOCCTR 기준 주소 부여
  6. 제어 섹션 분리: 여러 섹션을 독립적으로 추적하며 H/D/R/T/M/E 레코드 각각 생성
  7. 수정 레코드 생성: Format 4 외부 참조 명령어 발견 시 자동으로 M 레코드 추가

실행 방법

# 과제 1 실행 (inst_table.txt, input.txt가 같은 디렉터리에 있어야 함)
cd 과제1
gcc my_assembler_20203099.c -o assembler
./assembler
# → output_20203099.txt 생성

# 과제 2 실행
cd 과제2
gcc my_assembler_20203099-1.c -o assembler2
./assembler2
# → output_symtab.txt, output_littab.txt, output_objectcode.txt 생성

참고 자료

  • Leland L. Beck, System Software: An Introduction to Systems Programming, 3rd Edition
  • SIC/XE Architecture Specification (교재 부록)

About

컴퓨터학부 시스템프로그래밍 과목 프로젝트

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages