int bcopy_01() { BFILE* in; BFILE* out; int i = 0; in = bfopen("test.txt", "rb"); TEST_EXPECT(in != NULL); out = bfopen("copy.txt", "wb"); TEST_EXPECT(out != NULL); while (!bfeof(in)) { bfputc(bfgetc(in), out); i++; } bfclose(in); bfclose(out); if (i == LENGTH_OF_TEST_FILE) { TEST_SUCCESS; } else { TEST_FAILURE("didn't copy all expected data (total bytes copied incorrect)."); } }
void micro_write_file(BFILE* out, BFILE* in, freed_bstring name) { int written = 0; uint16_t store; size_t pos; // Check to make sure we don't have too many entries. if (micro_table_count >= MICRO_MAX_ENTRIES) { printd(LEVEL_WARNING, "ignoring file %s; maximum limit of %u files hit.\n", name.ref->data, MICRO_MAX_ENTRIES); exit(1); } // Copy data. store = (uint16_t)(bftell(out) / 2); while (!bfeof(in)) { bfputc(bfgetc(in), out); written++; } // Store the position so we can jump back to // this position. pos = bftell(out); // Write the entry. bfseek(out, micro_table_addr + micro_table_count * 2, SEEK_SET); bfiwrite(&store, out); store = written / 2; bfiwrite(&store, out); printd(LEVEL_VERBOSE, "wrote file %s at index %u (0x%04X words).\n", name.ref->data, micro_table_count, written / 2); micro_table_count++; // Seek back again. bfseek(out, pos, SEEK_SET); // Clear memory. bautodestroy(name); }
void micro_write_kernel(BFILE* out, BFILE* in, int count) { int written = 0, i; uint16_t store; size_t pos; // Copy data. while (!bfeof(in)) { bfputc(bfgetc(in), out); written++; } // Go back and write the filesystem // information if permitted. pos = bftell(out); bfseek(out, sizeof(uint16_t) * 2, SEEK_SET); bfiread(&store, out); bfseek(out, sizeof(uint16_t) * 2, SEEK_SET); if (store == 0xDEDE) { store = written / 2; bfiwrite(&store, out); } else printd(LEVEL_VERBOSE, "not writing end-of-kernel to 0x0002 (0x0002 is not 0xDEDE).\n"); bfseek(out, pos, SEEK_SET); printd(LEVEL_VERBOSE, "wrote kernel (0x%04X words).\n", written / 2); // Store the position and count of the micro table. micro_table_addr = pos; micro_table_count = 0; // Write out 20 NULL entries (1 addr and 1 size for each entry). store = 0x0000; for (i = 0; i < MICRO_MAX_ENTRIES * 2; i++) bfiwrite(&store, out); }
uint16_t aout_write(FILE* out, bool relocatable, bool intermediate) { struct aout_byte* current_outer; struct aout_byte* current_inner; struct lprov_entry* linker_provided = NULL; struct lprov_entry* linker_required = NULL; struct lprov_entry* linker_adjustment = NULL; struct lprov_entry* linker_section = NULL; struct lprov_entry* linker_output = NULL; struct lprov_entry* linker_temp = NULL; uint32_t mem_index, out_index; uint16_t inst; BFILE* temp = NULL; bstring bname, ename; uint16_t eaddr; bool did_find; bool shown_expr_warning = false; bool has_output = false; // Initialize out our extension table. code_offset += textn_init(start); // If relocatable, initialize out our relocation table. if (relocatable) code_offset += treloc_init(start); // First go through and evaluate all expressions that need to be. current_outer = start; out_index = code_offset; while (current_outer != NULL) { if (current_outer->type == AOUT_TYPE_METADATA_ORIGIN) { // Adjust memory address. out_index = current_outer->opcode; } else if (current_outer->type == AOUT_TYPE_METADATA_SECTION) { assert(current_outer->label != NULL); // We're exporting the current address as the beginning // of a section. if (!intermediate) ahalt(ERR_NOT_GENERATING_INTERMEDIATE_CODE, NULL); // Check to make sure outputs haven't previously been emitted. if (has_output) ahalt(ERR_OUTPUT_BEFORE_SECTION, NULL); // Create linker entry. linker_temp = lprov_create(current_outer->label, out_index); linker_temp->next = linker_section; linker_section = linker_temp; printd(LEVEL_VERBOSE, "LINK SECTION %s -> 0x%04X\n", current_outer->label, out_index); } else if (current_outer->type == AOUT_TYPE_METADATA_OUTPUT) { assert(current_outer->label != NULL); // We're exporting the current address as the beginning // of a section. if (!intermediate) ahalt(ERR_NOT_GENERATING_INTERMEDIATE_CODE, NULL); // Create linker entry. has_output = true; linker_temp = lprov_create(current_outer->label, out_index); linker_temp->next = linker_output; linker_output = linker_temp; printd(LEVEL_VERBOSE, "LINK OUTPUT 0x%04X -> %s\n", out_index, current_outer->label); } else if (current_outer->type == AOUT_TYPE_METADATA_EXPORT) { assert(current_outer->label != NULL); // We're exporting the address of this label in the // object table. if (!intermediate) ahalt(ERR_NOT_GENERATING_INTERMEDIATE_CODE, NULL); // Resolve label position. ename = bfromcstr(current_outer->label); eaddr = aout_get_label_address(ename); bdestroy(ename); // Create linker entry. linker_temp = lprov_create(current_outer->label, eaddr); linker_temp->next = linker_provided; linker_provided = linker_temp; printd(LEVEL_VERBOSE, "LINK REPLACE %s -> 0x%04X\n", current_outer->label, eaddr); } else if (current_outer->type == AOUT_TYPE_NORMAL && current_outer->expr != NULL) { if (current_outer->expr->type != EXPR_LABEL) { // This is either just a number or a more complicated expression, so // evaluate it using the preprocessor expression engine. if ((relocatable || intermediate) && !shown_expr_warning) { printd(LEVEL_WARNING, "warning: expressions will not be adjusted at link or relocation time.\n"); printd(LEVEL_WARNING, " ensure labels are not used as part of expressions.\n"); shown_expr_warning = true; } current_outer->raw_used = true; current_outer->raw = expr_evaluate(current_outer->expr, &aout_get_label_address, &ahalt_expression_exit_handler); expr_delete(current_outer->expr); current_outer->expr = NULL; } else { // If this is just a label, we can handle it directly (this allows // us to handle imported labels in intermediate code). current_inner = start; mem_index = code_offset; did_find = false; // Search for .IMPORT directives first. while (current_inner != NULL) { if (current_inner->type == AOUT_TYPE_METADATA_ORIGIN) { // Adjust memory address. mem_index = current_inner->opcode; } else if (current_inner->type == AOUT_TYPE_METADATA_IMPORT) { // An imported label (we don't need to adjust // memory index because the existance of this type // of entry doesn't affect executable size). if (!intermediate) ahalt(ERR_NOT_GENERATING_INTERMEDIATE_CODE, NULL); assert(current_outer->expr->data != NULL); if (strcmp(current_inner->label, ((bstring)current_outer->expr->data)->data) == 0) { // We don't actually know our position yet; // that will be handled by the linker! current_outer->raw = 0xFFFF; expr_delete(current_outer->expr); current_outer->expr = NULL; linker_temp = lprov_create(current_inner->label, out_index); linker_temp->next = linker_required; linker_required = linker_temp; printd(LEVEL_VERBOSE, "LINK REPLACE 0x%04X -> %s\n", out_index, current_inner->label); did_find = true; break; } } // Goto next. current_inner = current_inner->next; } // If it wasn't found in the .IMPORT directives, try searching // labels directly using the expression engine. if (!did_find) { // Replace the label position. current_outer->raw_used = true; current_outer->raw = expr_evaluate(current_outer->expr, &aout_get_label_address, &ahalt_expression_exit_handler); expr_delete(current_outer->expr); current_outer->expr = NULL; did_find = true; // We also need to add this entry to the adjustment // table for the linker since it also needs to adjust // internal label jumps in files when it concatenates // all of the object code together. linker_temp = lprov_create(NULL, out_index); linker_temp->next = linker_adjustment; linker_adjustment = linker_temp; printd(LEVEL_VERBOSE, "LINK ADJUST 0x%04X\n", out_index); } } } if (current_outer->type == AOUT_TYPE_NORMAL && current_outer->label == NULL) out_index += 1; current_outer = current_outer->next; } // If intermediate, we need to write out our linker table // as the absolute first thing in the file. if (intermediate) { fwrite(ldata_objfmt, 1, strlen(ldata_objfmt) + 1, out); objfile_save(out, linker_provided, linker_required, linker_adjustment, linker_section, linker_output); // Adjust the "true origin" for .ORIGIN directivies because // the linker table won't exist in the final result when // linked. true_origin = (uint16_t)ftell(out); } // Write out our extension table. textn_write(out); // If relocatable, write out our relocation table. if (relocatable) treloc_write(out); // Now write to the file. current_outer = start; while (current_outer != NULL) { if (current_outer->type == AOUT_TYPE_METADATA_ORIGIN) { // Adjust origin. fseek(out, true_origin + current_outer->opcode * 2 /* double because the number is in words, not bytes */, SEEK_SET); } else if (current_outer->type == AOUT_TYPE_METADATA_INCBIN) { // Include binary file. bname = ppfind_locate(bautofree(bfromcstr(current_outer->label))); if (bname == NULL) ahalt(ERR_UNABLE_TO_INCBIN, current_outer->label); temp = bfopen((const char*)(bname->data), "rb"); if (temp == NULL) ahalt(ERR_UNABLE_TO_INCBIN, current_outer->label); // Copy binary data. while (!bfeof(temp)) { // TODO: This could be faster if we didn't do it character // by character. fputc(bfgetc(temp), out); } // Finalize. bfclose(temp); bdestroy(bname); } else if (current_outer->type == AOUT_TYPE_NORMAL) { // Update the debugging symbol. dbgfmt_update_symbol_list(¤t_outer->symbols, (uint16_t)((ftell(out) - true_origin) / 2)); // Normal output. if (current_outer->raw_used == true) { inst = current_outer->raw; iwrite(&inst, out); } else if (current_outer->label == NULL) { inst = INSTRUCTION_CREATE(current_outer->opcode, current_outer->a, current_outer->b); iwrite(&inst, out); } } // Goto next in linked list. current_outer = current_outer->next; } fflush(out); return (uint16_t)((ftell(out) - true_origin) / 2); }
int main(int argc, char* argv[]) { bstring ldargs = bfromcstr(""); int i, result; unsigned int match = 0, unmatch = 0; char ca, ce; BFILE* expect; BFILE* actual; // Define arguments. struct arg_lit* show_help = arg_lit0("h", "help", "Show this help."); struct arg_lit* gen_relocatable = arg_lit0("r", "relocatable", "Generate relocatable code."); struct arg_lit* gen_intermediate = arg_lit0("i", "intermediate", "Generate intermediate code for use with the linker."); struct arg_lit* little_endian_mode = arg_lit0(NULL, "little-endian", "Use little endian serialization."); struct arg_file* input_file = arg_file1(NULL, NULL, "<file>", "The input assembly file."); struct arg_file* expect_file = arg_file0("e", "expect", "<file>", "The output file that contains expected output."); struct arg_file* actual_file = arg_file1("a", "actual", "<file>", "The output file where actual output will be placed."); struct arg_file* symbols_file = arg_file0("s", "debug-symbols", "<file>", "The debugging symbol output file."); struct arg_lit* fail_opt = arg_lit0("f", "fail", "The assembler is expected to fail and the actual output file should not exist on completion."); struct arg_file* path = arg_file1("p", NULL, "<path>", "The path to the assembler."); struct arg_lit* verbose = arg_litn("v", NULL, 0, LEVEL_EVERYTHING - LEVEL_DEFAULT, "Increase verbosity."); struct arg_lit* quiet = arg_litn("q", NULL, 0, LEVEL_DEFAULT - LEVEL_SILENT, "Decrease verbosity."); struct arg_end* end = arg_end(20); void* argtable[] = { show_help, gen_relocatable, gen_intermediate, little_endian_mode, symbols_file, input_file, expect_file, actual_file, fail_opt, path, verbose, quiet, end }; // Parse arguments. int nerrors = arg_parse(argc, argv, argtable); version_print(bautofree(bfromcstr("Assembler Test Driver"))); if (nerrors != 0 || show_help->count != 0 || (fail_opt->count == 0 && (expect_file->count == 0 || actual_file->count == 0))) { if (show_help->count != 0 && fail_opt->count == 0 && (expect_file->count == 0 || actual_file->count == 0)) printd(LEVEL_ERROR, "error: you must provide either -f or -e and -a.\n"); if (show_help->count != 0) arg_print_errors(stderr, end, "testasm"); printd(LEVEL_DEFAULT, "syntax:\n testasm"); arg_print_syntax(stderr, argtable, "\n"); printd(LEVEL_DEFAULT, "options:\n"); arg_print_glossary(stderr, argtable, " %-25s %s\n"); arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); return 1; } // Set verbosity level. debug_setlevel(LEVEL_DEFAULT + verbose->count - quiet->count); // Set global path variable. osutil_setarg0(bautofree(bfromcstr(argv[0]))); // Generate the argument list for the assembler. ldargs = bfromcstr(path->filename[0]); binsertch(ldargs, 0, 1, '"'); bconchar(ldargs, '"'); bconchar(ldargs, ' '); // Verbosity options. if (verbose->count > 0) { bconchar(ldargs, '-'); for (i = 0; i < verbose->count; i++) bconchar(ldargs, 'v'); bconchar(ldargs, ' '); } if (quiet->count > 0) { bconchar(ldargs, '-'); for (i = 0; i < quiet->count; i++) bconchar(ldargs, 'q'); bconchar(ldargs, ' '); } // Literal options. if (gen_relocatable->count > 0) { bconchar(ldargs, '-'); for (i = 0; i < gen_relocatable->count; i++) bconchar(ldargs, 'r'); bconchar(ldargs, ' '); } if (gen_intermediate->count > 0) { bconchar(ldargs, '-'); for (i = 0; i < gen_intermediate->count; i++) bconchar(ldargs, 'i'); bconchar(ldargs, ' '); } if (little_endian_mode->count > 0) { for (i = 0; i < little_endian_mode->count; i++) bcatcstr(ldargs, "--little-endian "); } // Unlink the actual file so that if we are expecting // failure, we won't return incorrectly. unlink(actual_file->filename[0]); // Output file. bcatcstr(ldargs, "-o \""); bcatcstr(ldargs, actual_file->filename[0]); bcatcstr(ldargs, "\" "); // Input file. bcatcstr(ldargs, "\""); bcatcstr(ldargs, input_file->filename[0]); bcatcstr(ldargs, "\" "); // Windows needs the whole command wrapped in quotes and slashes to be correct. // See http://stackoverflow.com/questions/2642551/windows-c-system-call-with-spaces-in-command. #ifdef _WIN32 binsertch(ldargs, 0, 1, '"'); bconchar(ldargs, '"'); #endif // Now run the assembler! result = system(ldargs->data); if (result != 0 && fail_opt->count == 0) { // Assembler returned error exit code. printd(LEVEL_ERROR, "error: expected success but assembler returned non-zero exit code (%i).\n", result); return 1; } else if (result == 0 && fail_opt->count >= 1) { // Assembler returned zero when failure was expected. printd(LEVEL_ERROR, "error: expected failure but assembler returned zero exit code.\n"); return 1; } else if (result != 0 && fail_opt->count >= 1) { // Assembler failed and we expected it to. Return success only // if the output file does not exist. actual = bfopen(actual_file->filename[0], "rb"); if (actual != NULL) { printd(LEVEL_ERROR, "error: expected failure but actual output file exists.\n"); bfclose(actual); return 1; } return 0; } // Open expect data. expect = bfopen(expect_file->filename[0], "rb"); if (expect == NULL) { // The expect file was not provided. printd(LEVEL_ERROR, "error: path to expect file does not exist.\n"); return 1; } // Open actual data. actual = bfopen(actual_file->filename[0], "rb"); if (actual == NULL) { // The expect file was not provided. bfclose(expect); printd(LEVEL_ERROR, "error: expected data but actual output file does not exist after running assembler.\n"); return 1; } // Now compare raw bytes. while (true) { if (!bfeof(actual) && !bfeof(expect)) { ca = bfgetc(actual); ce = bfgetc(expect); if (ca == ce) match++; else { printd(LEVEL_WARNING, "warning: byte at 0x%04X is different (got 0x%02X, expected 0x%02X)!\n", bftell(actual), ca, ce); unmatch++; } } else if (!bfeof(actual)) { ca = bfgetc(actual); printd(LEVEL_ERROR, "error: actual output contained trailing byte 0x%02X.\n", (unsigned char)ca); unmatch++; } else if (!bfeof(expect)) { ce = bfgetc(expect); printd(LEVEL_ERROR, "error: expected actual output to contain 0x%02X.\n", (unsigned char)ce); unmatch++; } else break; } if (unmatch > 0) { printd(LEVEL_ERROR, "error: actual output differs from expected output in content (%f%%, %i bytes different).\n", 100.f / (unmatch + match) * unmatch, unmatch); if (bftell(actual) != bftell(expect)) printd(LEVEL_ERROR, "error: actual output differs from expected output in length (%i bytes larger).\n", bftell(actual) - bftell(expect)); bfclose(actual); bfclose(expect); return 1; } // Close files and delete actual because we have // succeeded. bfclose(actual); bfclose(expect); unlink(actual_file->filename[0]); return 0; }