国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > LC-3虛擬機: C語言實現(xiàn)

LC-3虛擬機: C語言實現(xiàn)

時間:2023-06-30 20:36:01 | 來源:網(wǎng)站運營

時間:2023-06-30 20:36:01 來源:網(wǎng)站運營

LC-3虛擬機: C語言實現(xiàn):

0x1 目的

先前用80行實現(xiàn)了一個極為簡陋的虛擬機, 它雖然也很好的對 VM 進行了抽象(指令集, 程序, 棧; 取指令、對指令解析和求值), 但是似乎只做了 a+b 這件事, 并且除了 PC、 SP 外沒有涉及其他的寄存器。

這次實現(xiàn)的 LC-3 虛擬機, 實現(xiàn)了 LC-3 指令集, 區(qū)別有:

網(wǎng)絡上已經(jīng)有很多相應的實現(xiàn), 有的是250行完成, 有的則是400行實現(xiàn)。 我實現(xiàn)后做了必要的調整, 代碼更加清晰, 代碼長度約600行。只需要有 C 語言基礎就基本上可以看懂, 不過關于鍵盤中斷處理的部分需要在 Linux 下運行。

以下貼出我按照

操作后的一些補充記錄。完整代碼現(xiàn)已上傳 github,可以參考:

0x2 程序整體框架

VM 的作用就是讀取二進制的機器碼然后翻譯和執(zhí)行, 直到遇到 HALT 停機指令。 對于要執(zhí)行的 2048 小游戲來說, 先準備好 2048.obj 這個二進制文件, 讀取它的所有內容到內存(模擬出的內存);然后從 0x3000 這一起始位置開始讀取, 每次讀取一個指令(16bit), PC 寄存器加1, 解析和執(zhí)行這個剛讀取到的指令; 然后讀取下一條指令, 往復循環(huán)。此外, 為了能夠在解釋執(zhí)行 2048 游戲時能用鍵盤交互操作, 還需要注冊對應的信號和處理函數(shù), 并禁用輸入緩沖。整體的偽代碼如下:

int main(){ content = read_binary_file("2048.obj"); reg[R_PC] = 0x3000; signal(SIGINT, handle_interrupt); disable_input_buffering(); while (1) { instruction = mem_read(reg[R_PC]); reg[R_PC]++; ret = execute(instruction); if (ret != 0) break; } restore_input_buffering(); return 0;}

0x3 常規(guī)指令的處理 - sign_extend() 的修改

常規(guī)指令這里指代 trap 之外的指令, 例如 ADD, AND, NOT, LEA 等。參照指令集文檔, 以及 tutorial 中給出的 ADD 指令的例子, 很快就能寫好所有常規(guī)指令的 decode 和 execution 過程的代碼。

原版教程中給出了 sign_extend() 函數(shù)的實現(xiàn)以及多處調用, 看起來挺美好, 但如果稍微不聽話不完全按 tutorial 來, 容易踩坑:

  1. 多次調用 sign_extend() 可能寫錯參數(shù)
調用 sign_extend() 函數(shù)的地方有多處, 而每次調用都需要傳入兩個參數(shù): 一個是 mask, 另一個是位數(shù), 這兩個參數(shù)其實只用第二個就可以表達第一個, 因此有點冗余; 而多次調用這樣的冗余表達的代碼, 一不小心少寫一個 F 就導致在 debug 上浪費幾十分鐘(想想看, 2048的界面遲遲跑不出來)。

2. 內存越界問題

sign_extend() 更大的坑在于, 如果不打算用原文 tutorial 中對于內存讀寫封裝的 mem_write() 函數(shù)而是直接寫入表示內存的數(shù)組, 會發(fā)生內存越界。

原版 tutorial 以及譯文中, sign_extend()返回值是 uint16_t 類型, 通常作為 offset 存在, 和 pc 相加后作為一個內存地址, 然后從 memory 數(shù)組中獲取元素, 即:

uint16_t dr = (instr >> 9) & 0x7; uint16_t pc_offset = sign_extend(instr & 0x1ff, 9); mem_write(reg[R_PC] + pc_offset, reg[dr]);這里的問題在于: 如果 instr 最后9位全是1, 那么 sign_extend 后得到的是 65535, 而不是 -1; 而 65535 和 reg[R_PC] 相加, 由于這兩個都是 uint16_t 類型的, 那么肯定是會大于 65535。

這時候, 如果我沒有使用封裝的 mem_write() 函數(shù), 而是直接用 memory[reg[R_PC] + pc_offset] = reg[dr], 就會導致越界。

sign_extend 的原版實現(xiàn)是這樣的:

uint16_t sign_extend(uint16_t x, int bit_count) { if ((x >> (bit_count - 1)) & 1) { x |= (0xFFFF << bit_count); } return x;}實際上這里無論是輸入?yún)?shù)還是返回值, 用 int16_t 類型都是更加妥當?shù)? 避免了傳參和接收結果時的數(shù)據(jù)類型隱式轉換, 降低了心智負擔, 消除了不必要的內存越界問題:

int16_t sign_extend(int16_t x, int bit_count){ if ((x >> (bit_count - 1)) & 1) { x |= (0xFFFF << bit_count); } return x;}

0x4 TRAP 的處理

這一塊的處理我沒有太多想法, 原文 tutorial 的實現(xiàn), 是一種簡化方法, 是用操作系統(tǒng)自帶的 getchar 等函數(shù), 替代了手寫匯編, 也替代了輪詢導致的 CPU 高占用率問題。

0x5 工程構建 - makefile 的坑

寫 cmake 寫的過于熟練, 也過于啰嗦, 這次使用的是 makefile

all: clang main.c #-fno-omit-frame-pointer -fsanitize=address.PHONY: runrun: ./a.out ./2048.obj遇到了幾個問題, 稍作記錄:

  1. makefile 里的 make run 的坑。 代碼中本身是有做 SIGINT 注冊處理函數(shù)的, 那么運行期間按下 ctrl+c 后應該正常結束。 而如果是通過 make run 方式運行的, 會提示報錯:
make: *** [makefile:7:run] 錯誤 254
  1. makefile 的報錯是中文的, 而不是原版程序報錯的英文的 對于 segmentation fault, 希望報錯是英文原版的。不要強行翻譯啊。 例如原版輸出:
(base) zz@localhost% ./a.out ./2048.obj Control the game using WASD keys.zsh: segmentation fault (core dumped) ./a.out ./2048.obj而通過 make run 執(zhí)行得到的報錯則是:

(base) zz@localhost% make run./a.out ./2048.objControl the game using WASD keys.make: *** [makefile:7: run] 段錯誤 (核心已轉儲)
  1. 內存越界, 但 Address Sanitizer 檢測不出來
如前面 sign_extend() 函數(shù)中所提到的, 最開始用 uint16_t 作為參數(shù)和返回值類型, 并且直接寫入 memory 數(shù)組時由于索引超過 65535 (16bit最大數(shù)字)導致越界, 但開啟 ASAN 時并沒有報告出來。 原因暫時不明。

0x6 完整代碼

同時支持了 Windows, Linux 和 MacOS。

// The LC-3 VM implementation// based on https://www.jmeiners.com/lc3-vm/#include <stdint.h>#include <stdlib.h>#include <stdio.h>#include <stdbool.h>#include <signal.h>#if __linux__ || __APPLE__#include <unistd.h>#include <termios.h>#elif _MSC_VER#include <windows.h>#include <conio.h>#endif//#include "px_log.h"#if __ANDROID_API__ >= 8#include <android/log.h>#define PX_LOGE(...) do { / fprintf(stderr, ##__VA_ARGS__); fprintf(stderr, "/n"); / __android_log_print(ANDROID_LOG_WARN, "pixel", ##__VA_ARGS__); } while(0)#else // __ANDROID_API__ >= 8#include <stdio.h>#define PX_LOGE(...) do { / fprintf(stderr, ##__VA_ARGS__); fprintf(stderr, "/n"); } while(0)#endif // __ANDROID_API__ >= 8uint16_t memory[UINT16_MAX];int running = 1;enum{ R_R0 = 0, R_R1, R_R2, R_R3, R_R4, R_R5, R_R6, R_R7, R_PC, R_COND, // 是 PSR(Processor Status Register)寄存器的低3位, 即 PSR[2:0] R_COUNT};// registersuint16_t reg[R_COUNT];// opcode definitionenum{ OP_BR = 0, // branch OP_ADD, // add OP_LD, // load OP_ST, // store OP_JSR, // jump register OP_AND, // bitwise and OP_LDR, // load register OP_STR, // store register OP_RTI, // unused OP_NOT, // bitwise not OP_LDI, // load indirect OP_STI, // store indirect OP_JMP, // jump OP_RES, // reserved (unused) OP_LEA, // load effective address OP_TRAP // execute trap};// condition flagsenum{ FL_POS = 1 << 0, // P, positive FL_ZRO = 1 << 1, // Z, zero FL_NEG = 1 << 2, // N, negative};// trap codeenum{ TRAP_GETC = 0x20, // get character from keyboard, not echoed onto the terminal TRAP_OUT = 0x21, // output a character TRAP_PUTS = 0x22, // output a word string TRAP_IN = 0x23, // get character from keyboard, echoed onto the terminal TRAP_PUTSP = 0x24, // output a byte string TRAP_HALT = 0x25, // halt the program};enum{ MR_KBSR = 0xFE00, // keyboard status MR_KBDR = 0xFE02, // keyboard data};void mem_write(uint16_t address, uint16_t val){ memory[address] = val;}#if __linux__ || __APPLE__struct termios original_tio;void disable_input_buffering(){ tcgetattr(STDIN_FILENO, &original_tio); struct termios new_tio = original_tio; new_tio.c_lflag &= ~ICANON & ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);}void restore_input_buffering(){ tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);}uint16_t check_key(){ fd_set readfds; FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; return select(1, &readfds, NULL, NULL, &timeout) != 0;}#elif _MSC_VERHANDLE hStdin = INVALID_HANDLE_VALUE;DWORD fdwMode, fdwOldMode;void disable_input_buffering(){ hStdin = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(hStdin, &fdwOldMode); /* save old mode */ fdwMode = fdwOldMode ^ ENABLE_ECHO_INPUT /* no input echo */ ^ ENABLE_LINE_INPUT; /* return when one or more characters are available */ SetConsoleMode(hStdin, fdwMode); /* set new mode */ FlushConsoleInputBuffer(hStdin); /* clear buffer */}void restore_input_buffering(){ SetConsoleMode(hStdin, fdwOldMode);}uint16_t check_key(){ return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();}#endifvoid handle_interrupt(int signal){ restore_input_buffering(); printf("/n"); exit(-2);}uint16_t mem_read(uint16_t address){ if (address == MR_KBSR) { if (check_key()) { memory[MR_KBSR] = (1 << 15); memory[MR_KBDR] = getchar(); } else { memory[MR_KBSR] = 0; } } return memory[address];}int16_t sign_extend(int16_t x, int bit_count){ if ((x >> (bit_count - 1)) & 1) { x |= (0xFFFF << bit_count); } return x;}// 取 instr 的最后 count 位, 然后做帶符號的擴展int16_t get_instr_last_part(uint16_t instr, uint8_t count){ return sign_extend(instr & ((1U << count) - 1), count);}void update_flags(uint16_t r){ if (reg[r] == 0) { reg[R_COND] = FL_ZRO; } else if (reg[r] >> 15) /* a 1 in the left-most bit indicates negative */ { reg[R_COND] = FL_NEG; } else { reg[R_COND] = FL_POS; }}void do_add_instruction(uint16_t instr){ uint16_t dr = (instr >> 9) & 0x7; // destination register uint16_t sr1 = (instr >> 6) & 0x7; // first source register uint16_t imm_flag = (instr >> 5) & 0x1; // whether we are in immediate mode if (imm_flag) { uint16_t imm5 = get_instr_last_part(instr, 5); reg[dr] = reg[sr1] + imm5; } else { uint16_t sr2 = instr & 0x7; reg[dr] = reg[sr1] + reg[sr2]; } update_flags(dr);}void do_and_instruction(uint16_t instr){ uint16_t dr = (instr >> 9) & 0x7; // destination register uint16_t sr1 = (instr >> 6) & 0x7; // first source register uint16_t imm_flag = (instr >> 5) & 0x1; // whether we are in immediate mode if (imm_flag) { uint16_t imm5 = get_instr_last_part(instr, 5); reg[dr] = reg[sr1] & imm5; } else { uint16_t sr2 = instr & 0x7; reg[dr] = reg[sr1] & reg[sr2]; } update_flags(dr);}void do_br_instruction(uint16_t instr){ uint16_t pc_offset = get_instr_last_part(instr, 9); uint16_t cond_flag = (instr >> 9) & 0x7; if (cond_flag & reg[R_COND]) { reg[R_PC] = reg[R_PC] + pc_offset; }}void do_jmp_instruction(uint16_t instr){ uint16_t base_r = (instr >> 6) & 0x7; reg[R_PC] = reg[base_r];}void do_jsr_instruction(uint16_t instr){ reg[R_R7] = reg[R_PC]; uint16_t flag = (instr >> 11) & 0x1; if (flag) { uint16_t pc_offset = get_instr_last_part(instr, 11); reg[R_PC] = reg[R_PC] + pc_offset; } else { uint16_t base_r = (instr >> 6) & 0x7; reg[R_PC] = base_r; }}void do_ld_instruction(uint16_t instr){ uint16_t dr = (instr >> 9) & 0x7; uint16_t pc_offset = get_instr_last_part(instr, 9); reg[dr] = mem_read(reg[R_PC] + pc_offset); update_flags(dr);}void do_ldi_instruction(uint16_t instr){ uint16_t dr = (instr >> 9) & 0x7; // destination register uint16_t pc_offset = get_instr_last_part(instr, 9); reg[dr] = mem_read(mem_read(reg[R_PC] + pc_offset)); update_flags(dr);}void do_ldr_instruction(uint16_t instr){ uint16_t offset = instr & 0x3F; uint16_t base_r = (instr >> 6) & 0x7; uint16_t dr = (instr >> 9) & 0x7; reg[dr] = mem_read(reg[base_r] + offset); update_flags(dr);}void do_lea_instruction(uint16_t instr){ uint16_t pc_offset = instr & 0x1FF; uint16_t dr = (instr >> 9) & 0x7; reg[dr] = reg[R_PC] + pc_offset; update_flags(dr);}void do_not_instruction(uint16_t instr){ uint16_t dr = (instr >> 9) & 0x7; uint16_t sr = (instr >> 6) & 0x7; reg[dr] = ~(reg[sr]); update_flags(dr);}void do_ret_instruction(uint16_t instr){ reg[R_PC] = reg[R_R7];}void do_res_instruction(uint16_t instr){ abort();}void do_st_instruction(uint16_t instr){ uint16_t offset = get_instr_last_part(instr, 9); uint16_t sr = (instr >> 9) & 0x7; //memory[reg[R_PC] + offset] = reg[sr]; mem_write(reg[R_PC] + offset, reg[sr]);}void do_sti_instruction(uint16_t instr){ uint16_t offset = get_instr_last_part(instr, 9); uint16_t sr = (instr >> 9) & 0x7; //memory[memory[reg[R_PC] + offset]] = reg[sr]; mem_write(mem_read(reg[R_PC] + offset), reg[sr]);}void print_binary(uint16_t n) { char buffer[17]; /* 16 bits, plus room for a /0 */ buffer[16] = '/0'; for (int i = 15; i >= 0; --i) { /* convert bits from the end */ buffer[i] = '0' + (n & 1); /* '0' + bit => '0' or '1' */ n >>= 1; /* make the next bit the 'low bit' */ } /* At this point, `buffer` is `n` turned to a string of binary digits. */ printf("%s/n", buffer);}void do_str_instruction(uint16_t instr){ uint16_t sr = (instr >> 9) & 0x7; uint16_t base_r = (instr >> 6) & 0x7; int16_t offset = get_instr_last_part(instr, 6); memory[reg[base_r] + offset] = reg[sr]; // segfault}void do_puts_trap(){ uint16_t* c = memory + reg[R_R0]; while (*c) { putc((char)*c, stdout); c++; } fflush(stdout);}void do_in_trap(){ printf("Enter a character: "); char c = getchar(); putc(c, stdout); fflush(stdout); reg[R_R0] = (uint16_t)c; update_flags(R_R0); // ??}void do_putsp_trap(){ uint16_t* c = memory + reg[R_R0]; while (*c) { char char1 = (*c) & 0xFF; putc(char1, stdout); char char2 = (*c) >> 8; if (char2) { putc(char2, stdout); c++; } } fflush(stdout);}void do_halt_trap(){ puts("HALT"); fflush(stdout); running = 0;}void do_getc_trap(){ reg[R_R0] = (uint16_t)getchar(); update_flags(R_R0); // ??}void do_output_trap(){ putc((char)reg[R_R0], stdout); fflush(stdout);}void do_trap_instruction(uint16_t instr){ uint16_t trapvec = instr & 0xFF; reg[R_R7] = reg[R_PC]; // 處理 trap switch (trapvec) { case TRAP_GETC: PX_LOGE(" TRAP_GETC"); do_getc_trap(); break; case TRAP_OUT: PX_LOGE(" TRAP_OUT"); do_output_trap(); break; case TRAP_PUTS: PX_LOGE(" TRAP_PUTS"); do_puts_trap(); break; case TRAP_IN: PX_LOGE(" TRAP_IN"); do_in_trap(); break; case TRAP_PUTSP: PX_LOGE(" TRAP_PUTSP"); do_putsp_trap(); break; case TRAP_HALT: PX_LOGE(" TRAP_HALT"); do_halt_trap(); break; } reg[R_PC] = reg[R_R7]; // 由于本函數(shù)中的實現(xiàn), 并沒有修改 PC, 因此恢復 PC 看起來有點多余。 但穩(wěn)妥的做法確實應該是恢復 PC 的。}uint16_t swap16(uint16_t x){ return (x << 8) | (x >> 8);}static inline bool is_little_endian(){ volatile uint32_t data = 0x01234567; return (*((uint8_t*)(&data))) == 0x67;}void read_image_file(FILE* file){ uint16_t origin; // the origin tells us where in memory to place the image fread(&origin, sizeof(origin), 1, file); if (is_little_endian()) { origin = swap16(origin); } // we know the maximum file size so we only need one fread uint16_t max_read = UINT16_MAX - origin; uint16_t* p = memory + origin; size_t read = fread(p, sizeof(uint16_t), max_read, file); // swap to little endian if (is_little_endian()) { while (read-- > 0) { *p = swap16(*p); ++p; } }}int read_image(const char* image_path){ FILE* file = fopen(image_path, "rb"); if (!file) { return 0; } read_image_file(file); fclose(file); return 1;}int main(int argc, char** argv){ if (argc != 2) { fprintf(stderr, "Usage: %s /path/to/image-file/n", argv[0]); exit(1); } if (!read_image(argv[1])) { fprintf(stderr, "failed to load image: %s/n", argv[1]); exit(1); } // 注冊鍵盤監(jiān)聽信號 signal(SIGINT, handle_interrupt); disable_input_buffering(); reg[R_COND] = FL_ZRO; enum { PC_START = 0x3000 }; reg[R_PC] = PC_START; int op_cnt = 0; while (running) { uint16_t instr = mem_read(reg[R_PC]++); // fetch uint16_t op = instr >> 12; op_cnt++; // if (op_cnt == 10) break; // PX_LOGE("op_cnt = %d ", op_cnt); switch (op) { case OP_ADD: PX_LOGE("OP_ADD"); do_add_instruction(instr); break; case OP_AND: PX_LOGE("OP_AND"); do_and_instruction(instr); break; case OP_NOT: PX_LOGE("OP_NOT"); do_not_instruction(instr); break; case OP_BR: PX_LOGE("OP_BR"); do_br_instruction(instr); break; case OP_JMP: PX_LOGE("OP_JMP"); do_jmp_instruction(instr); break; case OP_JSR: PX_LOGE("OP_JSR"); do_jsr_instruction(instr); break; case OP_LD: PX_LOGE("OP_LD"); do_ld_instruction(instr); break; case OP_LDI: PX_LOGE("OP_LDI"); do_ldi_instruction(instr); break; case OP_LDR: PX_LOGE("OP_LDR"); do_ldr_instruction(instr); break; case OP_LEA: PX_LOGE("OP_LEA"); do_lea_instruction(instr); break; case OP_ST: PX_LOGE("OP_ST"); do_st_instruction(instr); break; case OP_STI: PX_LOGE("OP_STI"); do_sti_instruction(instr); break; case OP_STR: PX_LOGE("OP_STR"); do_str_instruction(instr); break; case OP_TRAP: PX_LOGE("OP_TRAP"); do_trap_instruction(instr); break; case OP_RES: case OP_RTI: default: abort(); break; } } restore_input_buffering(); return 0;}

0x7 References

關鍵詞:實現(xiàn),語言,虛擬

74
73
25
news

版權所有? 億企邦 1997-2025 保留一切法律許可權利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關閉