Support new radiance image format
92cdb0c5293447964be053214fac403b49193ac3ec07c902576346ebaa205535
1 parent
aef68e69
README
+8 -5
| 28 | 28 | ||
| 29 | 29 | Headless: |
|
| 30 | 30 | ||
| 31 | 31 | $ bin/emulator -run <program.bin> [<option>..] |
|
| 32 | 32 | ||
| 33 | - | The emulator loads a flat binary along with optional data sections: |
|
| 33 | + | The emulator loads a self-contained Radiance RV64 image: |
|
| 34 | 34 | ||
| 35 | - | <program.bin> Program text (machine instructions) |
|
| 36 | - | <program.bin>.ro.data Read-only data section |
|
| 37 | - | <program.bin>.rw.data Read-write data section |
|
| 38 | - | <program.bin>.debug Debug info (optional, for source locations) |
|
| 35 | + | Header "RAD0", version, and section sizes |
|
| 36 | + | Text Machine instructions |
|
| 37 | + | Read-only data Loaded at 0x00010000 |
|
| 38 | + | Read-write data Loaded at 0x00FFFFF0 |
|
| 39 | + | ||
| 40 | + | Legacy flat binaries with optional `.ro.data` and `.rw.data` sidecars remain |
|
| 41 | + | supported. A `<program>.debug` sidecar may be used for source locations. |
|
| 39 | 42 | ||
| 40 | 43 | ||
| 41 | 44 | OPTIONS |
|
| 42 | 45 | ||
| 43 | 46 | -run Run headless (no TUI). |
emulator.c
+125 -27
| 72 | 72 | /* TTY escape codes. */ |
|
| 73 | 73 | #define TTY_CLEAR "\033[2J\033[H" |
|
| 74 | 74 | #define TTY_GOTO_RC "\033[%d;%dH" |
|
| 75 | 75 | /* Exit code returned on EBREAK. */ |
|
| 76 | 76 | #define EBREAK_EXIT_CODE 133 |
|
| 77 | + | /* Self-contained Radiance image header. */ |
|
| 78 | + | #define IMAGE_MAGIC 0x30444152U |
|
| 79 | + | #define IMAGE_VERSION 1U |
|
| 80 | + | #define IMAGE_HEADER_SIZE 20U |
|
| 77 | 81 | ||
| 78 | 82 | /* Registers displayed in the TUI, in order. */ |
|
| 79 | 83 | static const reg_t registers_displayed[] = { |
|
| 80 | 84 | SP, FP, RA, A0, A1, A2, A3, A4, A5, A6, A7, T0, T1, T2, T3, T4, T5, T6 |
|
| 81 | 85 | }; |
| 1326 | 1330 | ); |
|
| 1327 | 1331 | } |
|
| 1328 | 1332 | return u_size; |
|
| 1329 | 1333 | } |
|
| 1330 | 1334 | ||
| 1335 | + | /* Decode a little-endian word without relying on host alignment. */ |
|
| 1336 | + | static u32 decode_u32_le(const u8 *bytes) { |
|
| 1337 | + | return (u32)bytes[0] | ((u32)bytes[1] << 8) | ((u32)bytes[2] << 16) | |
|
| 1338 | + | ((u32)bytes[3] << 24); |
|
| 1339 | + | } |
|
| 1340 | + | ||
| 1341 | + | /* Read exactly size bytes from file into guest memory at offset. */ |
|
| 1342 | + | static void load_image_section( |
|
| 1343 | + | FILE *file, u32 offset, u32 size, const char *label |
|
| 1344 | + | ) { |
|
| 1345 | + | if (size == 0) |
|
| 1346 | + | return; |
|
| 1347 | + | ||
| 1348 | + | size_t read = fread(&memory[offset], 1, size, file); |
|
| 1349 | + | if (read != size) |
|
| 1350 | + | bail( |
|
| 1351 | + | "could not read entire %s section: read %zu bytes, expected %u " |
|
| 1352 | + | "bytes", |
|
| 1353 | + | label, |
|
| 1354 | + | read, |
|
| 1355 | + | size |
|
| 1356 | + | ); |
|
| 1357 | + | } |
|
| 1358 | + | ||
| 1331 | 1359 | /* Prepare the environment block (argv) on the guest stack. */ |
|
| 1332 | 1360 | static void prepare_env(struct cpu *cpu, int argc, char **argv) { |
|
| 1333 | 1361 | if (argc < 0 || argv == NULL) |
|
| 1334 | 1362 | argc = 0; |
|
| 1335 | 1363 |
| 1474 | 1502 | ||
| 1475 | 1503 | /* Load the program binary and data sections into memory. */ |
|
| 1476 | 1504 | static void program_init(struct cpu *cpu, const char *filepath) { |
|
| 1477 | 1505 | program_bytes = 0; |
|
| 1478 | 1506 | data_bytes = 0; |
|
| 1479 | - | rodata_bytes = load_section( |
|
| 1480 | - | filepath, "ro.data", DATA_RO_OFFSET, DATA_MEMORY_START, "ro.data" |
|
| 1481 | - | ); |
|
| 1482 | - | program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE); |
|
| 1483 | - | ||
| 1484 | 1507 | if (g_opts.debug_enabled) |
|
| 1485 | 1508 | debug_load(filepath); |
|
| 1486 | 1509 | ||
| 1487 | 1510 | FILE *file = fopen(filepath, "rb"); |
|
| 1488 | 1511 | if (!file) |
| 1490 | 1513 | if (fseek(file, 0, SEEK_END) != 0) { |
|
| 1491 | 1514 | fclose(file); |
|
| 1492 | 1515 | bail("failed to seek program '%s'", filepath); |
|
| 1493 | 1516 | } |
|
| 1494 | 1517 | long size = ftell(file); |
|
| 1495 | - | if (size <= 0 || size > PROGRAM_SIZE) { |
|
| 1518 | + | if (size <= 0) { |
|
| 1496 | 1519 | fclose(file); |
|
| 1497 | - | bail( |
|
| 1498 | - | "invalid file size: %ld; maximum program size is %d bytes", |
|
| 1499 | - | size, |
|
| 1500 | - | PROGRAM_SIZE |
|
| 1501 | - | ); |
|
| 1502 | - | } |
|
| 1503 | - | if (program_base + (u32)size > DATA_MEMORY_START) { |
|
| 1504 | - | fclose(file); |
|
| 1505 | - | bail("text section exceeds available program memory"); |
|
| 1520 | + | bail("invalid file size: %ld", size); |
|
| 1506 | 1521 | } |
|
| 1507 | 1522 | if (fseek(file, 0, SEEK_SET) != 0) { |
|
| 1508 | 1523 | fclose(file); |
|
| 1509 | 1524 | bail("failed to rewind program '%s'", filepath); |
|
| 1510 | 1525 | } |
|
| 1511 | 1526 | ||
| 1512 | - | usize read = fread(&memory[program_base], 1, size, file); |
|
| 1513 | - | fclose(file); |
|
| 1514 | - | if (read != (size_t)size) |
|
| 1515 | - | bail("could not read entire file"); |
|
| 1516 | - | ||
| 1517 | - | program_bytes = (u32)size; |
|
| 1518 | - | cpu->programsize = (u32)size / sizeof(instr_t); |
|
| 1519 | - | ||
| 1520 | 1527 | u32 data_limit = DATA_MEMORY_START + g_opts.data_memory_size; |
|
| 1521 | 1528 | if (data_limit > g_opts.memory_size) |
|
| 1522 | 1529 | data_limit = g_opts.memory_size; |
|
| 1523 | 1530 | ||
| 1524 | - | data_bytes = load_section( |
|
| 1525 | - | filepath, "rw.data", DATA_MEMORY_START, data_limit, "rw.data" |
|
| 1526 | - | ); |
|
| 1527 | - | cpu->pc = program_base; |
|
| 1531 | + | u8 header[IMAGE_HEADER_SIZE]; |
|
| 1532 | + | size_t header_read = fread(header, 1, sizeof(header), file); |
|
| 1533 | + | bool is_image = |
|
| 1534 | + | header_read >= sizeof(u32) && decode_u32_le(header) == IMAGE_MAGIC; |
|
| 1535 | + | ||
| 1536 | + | if (is_image) { |
|
| 1537 | + | if (header_read != sizeof(header)) { |
|
| 1538 | + | fclose(file); |
|
| 1539 | + | bail("truncated Radiance image header"); |
|
| 1540 | + | } |
|
| 1541 | + | ||
| 1542 | + | u32 version = decode_u32_le(&header[4]); |
|
| 1543 | + | program_bytes = decode_u32_le(&header[8]); |
|
| 1544 | + | rodata_bytes = decode_u32_le(&header[12]); |
|
| 1545 | + | data_bytes = decode_u32_le(&header[16]); |
|
| 1546 | + | ||
| 1547 | + | if (version != IMAGE_VERSION) { |
|
| 1548 | + | fclose(file); |
|
| 1549 | + | bail("unsupported Radiance image version: %u", version); |
|
| 1550 | + | } |
|
| 1551 | + | if (program_bytes == 0 || program_bytes % sizeof(instr_t) != 0) { |
|
| 1552 | + | fclose(file); |
|
| 1553 | + | bail( |
|
| 1554 | + | "invalid text section size in Radiance image: %u", program_bytes |
|
| 1555 | + | ); |
|
| 1556 | + | } |
|
| 1557 | + | u64 image_size = |
|
| 1558 | + | (u64)IMAGE_HEADER_SIZE + program_bytes + rodata_bytes + data_bytes; |
|
| 1559 | + | if (image_size != (u64)size) { |
|
| 1560 | + | fclose(file); |
|
| 1561 | + | bail( |
|
| 1562 | + | "Radiance image size does not match header: file has %ld " |
|
| 1563 | + | "bytes, header requires %llu", |
|
| 1564 | + | size, |
|
| 1565 | + | (unsigned long long)image_size |
|
| 1566 | + | ); |
|
| 1567 | + | } |
|
| 1568 | + | if (program_bytes > PROGRAM_SIZE) { |
|
| 1569 | + | fclose(file); |
|
| 1570 | + | bail( |
|
| 1571 | + | "text section too large: %u bytes; maximum is %u bytes", |
|
| 1572 | + | program_bytes, |
|
| 1573 | + | PROGRAM_SIZE |
|
| 1574 | + | ); |
|
| 1575 | + | } |
|
| 1576 | + | if ((u64)DATA_RO_OFFSET + rodata_bytes > DATA_MEMORY_START) { |
|
| 1577 | + | fclose(file); |
|
| 1578 | + | bail("read-only data section exceeds available program memory"); |
|
| 1579 | + | } |
|
| 1580 | + | program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE); |
|
| 1581 | + | if ((u64)program_base + program_bytes > DATA_MEMORY_START) { |
|
| 1582 | + | fclose(file); |
|
| 1583 | + | bail("text section exceeds available program memory"); |
|
| 1584 | + | } |
|
| 1585 | + | if ((u64)DATA_MEMORY_START + data_bytes > data_limit) { |
|
| 1586 | + | fclose(file); |
|
| 1587 | + | bail("read-write data section exceeds available data memory"); |
|
| 1588 | + | } |
|
| 1589 | + | ||
| 1590 | + | load_image_section(file, program_base, program_bytes, "text"); |
|
| 1591 | + | load_image_section( |
|
| 1592 | + | file, DATA_RO_OFFSET, rodata_bytes, "read-only data" |
|
| 1593 | + | ); |
|
| 1594 | + | load_image_section( |
|
| 1595 | + | file, DATA_MEMORY_START, data_bytes, "read-write data" |
|
| 1596 | + | ); |
|
| 1597 | + | } else { |
|
| 1598 | + | /* Legacy flat binaries use optional sidecar data files. */ |
|
| 1599 | + | rewind(file); |
|
| 1600 | + | rodata_bytes = load_section( |
|
| 1601 | + | filepath, "ro.data", DATA_RO_OFFSET, DATA_MEMORY_START, "ro.data" |
|
| 1602 | + | ); |
|
| 1603 | + | program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE); |
|
| 1604 | + | if (size > PROGRAM_SIZE) { |
|
| 1605 | + | fclose(file); |
|
| 1606 | + | bail( |
|
| 1607 | + | "invalid file size: %ld; maximum program size is %d bytes", |
|
| 1608 | + | size, |
|
| 1609 | + | PROGRAM_SIZE |
|
| 1610 | + | ); |
|
| 1611 | + | } |
|
| 1612 | + | if ((u64)program_base + (u32)size > DATA_MEMORY_START) { |
|
| 1613 | + | fclose(file); |
|
| 1614 | + | bail("text section exceeds available program memory"); |
|
| 1615 | + | } |
|
| 1616 | + | program_bytes = (u32)size; |
|
| 1617 | + | load_image_section(file, program_base, program_bytes, "program"); |
|
| 1618 | + | data_bytes = load_section( |
|
| 1619 | + | filepath, "rw.data", DATA_MEMORY_START, data_limit, "rw.data" |
|
| 1620 | + | ); |
|
| 1621 | + | } |
|
| 1622 | + | fclose(file); |
|
| 1623 | + | ||
| 1624 | + | cpu->programsize = program_bytes / sizeof(instr_t); |
|
| 1625 | + | cpu->pc = program_base; |
|
| 1528 | 1626 | } |
|
| 1529 | 1627 | ||
| 1530 | 1628 | /* Execute a single instruction. */ |
|
| 1531 | 1629 | static void cpu_execute(struct cpu *cpu, enum display display, bool headless) { |
|
| 1532 | 1630 | if (cpu_out_of_bounds(cpu)) { |