diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d290339e6..b78b2cb49a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,27 +200,29 @@ install(FILES ${C_HEADERS} DESTINATION ${C_HEADERS_DEST}) install(FILES "${CMAKE_SOURCE_DIR}/std/bootstrap.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/builtin.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/compiler_rt.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/cstr.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/debug.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/elf.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/empty.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/endian.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/errno.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/linux.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/linux_x86_64.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/math.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/net.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/os.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/rand.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/rand_test.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/str.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner_libc.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner_nolibc.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/net.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/os.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/str.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/cstr.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/linux.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/errno.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/rand.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/rand_test.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/math.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/linux_x86_64.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/empty.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/debug.zig" DESTINATION "${ZIG_STD_DEST}") add_executable(run_tests ${TEST_SOURCES}) target_link_libraries(run_tests) diff --git a/doc/style.md b/doc/style.md index 1fb2f5f615..058b1a405e 100644 --- a/doc/style.md +++ b/doc/style.md @@ -62,7 +62,7 @@ const xml_document = \\ \\ \\ - ; +; struct XmlParser {} // The initials BE (Big Endian) are just another word in Zig identifier names. diff --git a/example/cat/main.zig b/example/cat/main.zig index c10db65570..fa45f2a8ee 100644 --- a/example/cat/main.zig +++ b/example/cat/main.zig @@ -14,7 +14,8 @@ pub fn main(args: [][]u8) -> %void { } else if (arg[0] == '-') { return usage(exe); } else { - var is = io.InStream.open(arg) %% |err| { + var is: io.InStream = undefined; + is.open(arg) %% |err| { %%io.stderr.printf("Unable to open file: "); %%io.stderr.printf(@errName(err)); %%io.stderr.printf("\n"); diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index ebf6b0ce52..559284b655 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -7,7 +7,7 @@ pub fn main(args: [][]u8) -> %void { %%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n"); var seed: [@sizeOf(usize)]u8 = undefined; - %%os.get_random_bytes(seed); + %%os.getRandomBytes(seed); var rand: Rand = undefined; rand.init(([]usize)(seed)[0]); diff --git a/src/all_types.hpp b/src/all_types.hpp index 4785c21d20..de47409d69 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1232,6 +1232,7 @@ struct CodeGen { TypeTableEntry *entry_os_enum; TypeTableEntry *entry_arch_enum; TypeTableEntry *entry_environ_enum; + TypeTableEntry *entry_oformat_enum; TypeTableEntry *entry_atomic_order_enum; } builtin_types; @@ -1256,6 +1257,7 @@ struct CodeGen { uint32_t target_os_index; uint32_t target_arch_index; uint32_t target_environ_index; + uint32_t target_oformat_index; LLVMTargetMachineRef target_machine; LLVMZigDIFile *dummy_di_file; bool is_native_target; diff --git a/src/analyze.cpp b/src/analyze.cpp index 5f4e229f7e..4899ec533d 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5296,6 +5296,9 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry } else if (buf_eql_str(var_name, "environ")) { const_val->data.x_enum.tag = g->target_environ_index; return g->builtin_types.entry_environ_enum; + } else if (buf_eql_str(var_name, "object_format")) { + const_val->data.x_enum.tag = g->target_oformat_index; + return g->builtin_types.entry_oformat_enum; } else { add_node_error(g, *str_node, buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(var_name))); diff --git a/src/codegen.cpp b/src/codegen.cpp index e840e42c06..3b954da4f6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4576,6 +4576,33 @@ static void define_builtin_types(CodeGen *g) { g->builtin_types.entry_environ_enum = entry; } + { + TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdEnum); + entry->deep_const = true; + entry->zero_bits = true; // only allowed at compile time + buf_init_from_str(&entry->name, "@ObjectFormat"); + uint32_t field_count = target_oformat_count(); + entry->data.enumeration.field_count = field_count; + entry->data.enumeration.fields = allocate(field_count); + for (uint32_t i = 0; i < field_count; i += 1) { + TypeEnumField *type_enum_field = &entry->data.enumeration.fields[i]; + ZigLLVM_ObjectFormatType oformat = get_target_oformat(i); + type_enum_field->name = buf_create_from_str(get_target_oformat_name(oformat)); + type_enum_field->value = i; + type_enum_field->type_entry = g->builtin_types.entry_void; + + if (oformat == g->zig_target.oformat) { + g->target_oformat_index = i; + } + } + entry->data.enumeration.complete = true; + + TypeTableEntry *tag_type_entry = get_smallest_unsigned_int_type(g, field_count); + entry->data.enumeration.tag_type = tag_type_entry; + + g->builtin_types.entry_oformat_enum = entry; + } + { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdEnum); entry->deep_const = true; diff --git a/src/target.cpp b/src/target.cpp index 07a7bb0098..18573be4c3 100644 --- a/src/target.cpp +++ b/src/target.cpp @@ -137,6 +137,31 @@ static const ZigLLVM_EnvironmentType environ_list[] = { ZigLLVM_CoreCLR, }; +static const ZigLLVM_ObjectFormatType oformat_list[] = { + ZigLLVM_UnknownObjectFormat, + ZigLLVM_COFF, + ZigLLVM_ELF, + ZigLLVM_MachO, +}; + +int target_oformat_count(void) { + return array_length(oformat_list); +} + +const ZigLLVM_ObjectFormatType get_target_oformat(int index) { + return oformat_list[index]; +} + +const char *get_target_oformat_name(ZigLLVM_ObjectFormatType oformat) { + switch (oformat) { + case ZigLLVM_UnknownObjectFormat: return "unknown"; + case ZigLLVM_COFF: return "coff"; + case ZigLLVM_ELF: return "elf"; + case ZigLLVM_MachO: return "macho"; + } + zig_unreachable(); +} + int target_arch_count(void) { return array_length(arch_list); } diff --git a/src/target.hpp b/src/target.hpp index fc2bdb3a4e..d758df1519 100644 --- a/src/target.hpp +++ b/src/target.hpp @@ -52,6 +52,11 @@ const char *get_target_os_name(ZigLLVM_OSType os_type); int target_environ_count(void); ZigLLVM_EnvironmentType get_target_environ(int index); + +int target_oformat_count(void); +const ZigLLVM_ObjectFormatType get_target_oformat(int index); +const char *get_target_oformat_name(ZigLLVM_ObjectFormatType oformat); + void get_native_target(ZigTarget *target); void get_unknown_target(ZigTarget *target); diff --git a/std/debug.zig b/std/debug.zig index 41034fa393..2636799487 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -1,21 +1,123 @@ const Allocator = @import("mem.zig").Allocator; const io = @import("io.zig"); +const os = @import("os.zig"); +const elf = @import("elf.zig"); + +pub error MissingDebugInfo; +pub error InvalidDebugInfo; +pub error UnsupportedDebugInfo; pub fn assert(b: bool) { if (!b) unreachable{} } -pub fn printStackTrace() { - var maybe_fp: ?&const u8 = @frameAddress(); - while (true) { - const fp = maybe_fp ?? break; - const return_address = *(&const usize)(usize(fp) + @sizeOf(usize)); - %%io.stderr.printInt(usize, return_address); - %%io.stderr.printf("\n"); - maybe_fp = *(&const ?&const u8)(fp); +pub fn printStackTrace() -> %void { + %return writeStackTrace(&io.stderr); + %return io.stderr.flush(); +} + +pub fn writeStackTrace(out_stream: &io.OutStream) -> %void { + switch (@compileVar("object_format")) { + elf => { + var st: ElfStackTrace = undefined; + %return io.openSelfExe(&st.self_exe_stream); + defer %return st.self_exe_stream.close(); + + %return st.elf.openStream(&global_allocator, &st.self_exe_stream); + defer %return st.elf.close(); + + st.aranges = %return st.elf.findSection(".debug_aranges"); + + var maybe_fp: ?&const u8 = @frameAddress(); + while (true) { + const fp = maybe_fp ?? break; + const return_address = *(&const usize)(usize(fp) + @sizeOf(usize)); + + // read .debug_aranges to find out which compile unit the address is in + const debug_info_offset = %return debugInfoOffset(&st, return_address); + + %return out_stream.printInt(usize, return_address); + %return out_stream.printf(" -> "); + %return out_stream.printInt(u64, debug_info_offset); + %return out_stream.printf("\n"); + maybe_fp = *(&const ?&const u8)(fp); + } + }, + coff => { + out_stream.write("(stack trace unavailable for COFF object format)\n"); + }, + macho => { + out_stream.write("(stack trace unavailable for Mach-O object format)\n"); + }, + unknown => { + out_stream.write("(stack trace unavailable for unknown object format)\n"); + }, } } +struct ElfStackTrace { + self_exe_stream: io.InStream, + elf: elf.Elf, + aranges: ?&elf.SectionHeader, +} + +fn debugInfoOffset(st: &ElfStackTrace, target_address: usize) -> %u64 { + // when there is no .debug_aranges section, offset into debug info is 0x0 + const aranges = st.aranges ?? return 0; + + %return st.elf.seekToSection(aranges); + + const first_32_bits = %return st.self_exe_stream.readIntLe(u32); + const is_64 = (first_32_bits == 0xffffffff); + const unit_length = if (is_64) { + %return st.self_exe_stream.readIntLe(u64) + } else { + if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; + first_32_bits + }; + var unit_index: u64 = 0; + + while (unit_index < unit_length) { + const version = %return st.self_exe_stream.readIntLe(u16); + if (version != 2) return error.InvalidDebugInfo; + unit_index += 2; + + const debug_info_offset = if (is_64) { + unit_index += 4; + %return st.self_exe_stream.readIntLe(u64) + } else { + unit_index += 2; + %return st.self_exe_stream.readIntLe(u32) + }; + + const address_size = %return st.self_exe_stream.readByte(); + if (address_size > 8) return error.UnsupportedDebugInfo; + unit_index += 1; + + const segment_size = %return st.self_exe_stream.readByte(); + if (segment_size > 0) return error.UnsupportedDebugInfo; + unit_index += 1; + + const align = segment_size + 2 * address_size; + const padding = st.self_exe_stream.offset % align; + %return st.self_exe_stream.seekForward(padding); + unit_index += padding; + + while (true) { + const address = %return st.self_exe_stream.readVarInt(false, u64, address_size); + const length = %return st.self_exe_stream.readVarInt(false, u64, address_size); + unit_index += align; + if (address == 0 && length == 0) break; + + if (target_address >= address && target_address < address + length) { + return debug_info_offset; + } + } + } + + return error.MissingDebugInfo; +} + pub var global_allocator = Allocator { .allocFn = globalAlloc, .reallocFn = globalRealloc, diff --git a/std/elf.zig b/std/elf.zig new file mode 100644 index 0000000000..f98937633d --- /dev/null +++ b/std/elf.zig @@ -0,0 +1,261 @@ +const io = @import("io.zig"); +const str = @import("str.zig"); +const math = @import("math.zig"); +const mem = @import("mem.zig"); +const debug = @import("debug.zig"); + +pub error InvalidFormat; + +pub const SHT_NULL = 0; +pub const SHT_PROGBITS = 1; +pub const SHT_SYMTAB = 2; +pub const SHT_STRTAB = 3; +pub const SHT_RELA = 4; +pub const SHT_HASH = 5; +pub const SHT_DYNAMIC = 6; +pub const SHT_NOTE = 7; +pub const SHT_NOBITS = 8; +pub const SHT_REL = 9; +pub const SHT_SHLIB = 10; +pub const SHT_DYNSYM = 11; +pub const SHT_INIT_ARRAY = 14; +pub const SHT_FINI_ARRAY = 15; +pub const SHT_PREINIT_ARRAY = 16; +pub const SHT_GROUP = 17; +pub const SHT_SYMTAB_SHNDX = 18; +pub const SHT_LOOS = 0x60000000; +pub const SHT_HIOS = 0x6fffffff; +pub const SHT_LOPROC = 0x70000000; +pub const SHT_HIPROC = 0x7fffffff; +pub const SHT_LOUSER = 0x80000000; +pub const SHT_HIUSER = 0xffffffff; + +pub enum FileType { + Relocatable, + Executable, + Shared, + Core, +} + +pub enum Arch { + Sparc, + x86, + Mips, + PowerPc, + Arm, + SuperH, + IA_64, + x86_64, + AArch64, +} + +pub struct SectionHeader { + name: u32, + sh_type: u32, + flags: u64, + addr: u64, + offset: u64, + size: u64, + link: u32, + info: u32, + addr_align: u64, + ent_size: u64, +} + +pub struct Elf { + in_stream: &io.InStream, + auto_close_stream: bool, + is_64: bool, + is_big_endian: bool, + file_type: FileType, + arch: Arch, + entry_addr: u64, + program_header_offset: u64, + section_header_offset: u64, + string_section_index: u64, + string_section: &SectionHeader, + section_headers: []SectionHeader, + allocator: &mem.Allocator, + prealloc_stream: io.InStream, + + /// Call close when done. + pub fn openFile(elf: &Elf, allocator: &mem.Allocator, path: []const u8) -> %void { + %return elf.prealloc_stream.open(path); + %return elf.openStream(allocator, &elf.prealloc_stream); + elf.auto_close_stream = true; + } + + /// Call close when done. + pub fn openStream(elf: &Elf, allocator: &mem.Allocator, stream: &io.InStream) -> %void { + elf.allocator = allocator; + elf.in_stream = stream; + elf.auto_close_stream = false; + + var magic: [4]u8 = undefined; + %return elf.in_stream.readNoEof(magic); + if (!str.eql(magic, "\x7fELF")) return error.InvalidFormat; + + elf.is_64 = switch (%return elf.in_stream.readByte()) { + 1 => false, + 2 => true, + else => return error.InvalidFormat, + }; + + elf.is_big_endian = switch (%return elf.in_stream.readByte()) { + 1 => false, + 2 => true, + else => return error.InvalidFormat, + }; + + const version_byte = %return elf.in_stream.readByte(); + if (version_byte != 1) return error.InvalidFormat; + + // skip over padding + %return elf.in_stream.seekForward(9); + + elf.file_type = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) { + 1 => FileType.Relocatable, + 2 => FileType.Executable, + 3 => FileType.Shared, + 4 => FileType.Core, + else => return error.InvalidFormat, + }; + + elf.arch = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) { + 0x02 => Arch.Sparc, + 0x03 => Arch.x86, + 0x08 => Arch.Mips, + 0x14 => Arch.PowerPc, + 0x28 => Arch.Arm, + 0x2A => Arch.SuperH, + 0x32 => Arch.IA_64, + 0x3E => Arch.x86_64, + 0xb7 => Arch.AArch64, + else => return error.InvalidFormat, + }; + + const elf_version = %return elf.in_stream.readInt(elf.is_big_endian, u32); + if (elf_version != 1) return error.InvalidFormat; + + if (elf.is_64) { + elf.entry_addr = %return elf.in_stream.readInt(elf.is_big_endian, u64); + elf.program_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); + elf.section_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); + } else { + elf.entry_addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + elf.program_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + elf.section_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + } + + // skip over flags + %return elf.in_stream.seekForward(4); + + const header_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); + if ((elf.is_64 && header_size != 64) || + (!elf.is_64 && header_size != 52)) + { + return error.InvalidFormat; + } + + const ph_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); + const ph_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16); + const sh_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); + const sh_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16); + elf.string_section_index = u64(%return elf.in_stream.readInt(elf.is_big_endian, u16)); + + if (elf.string_section_index >= sh_entry_count) return error.InvalidFormat; + + const sh_byte_count = u64(sh_entry_size) * u64(sh_entry_count); + const end_sh = %return math.addOverflow(u64, elf.section_header_offset, sh_byte_count); + const ph_byte_count = u64(ph_entry_size) * u64(ph_entry_count); + const end_ph = %return math.addOverflow(u64, elf.program_header_offset, ph_byte_count); + + const stream_end = %return elf.in_stream.endPos(); + if (stream_end < end_sh || stream_end < end_ph) { + return error.InvalidFormat; + } + + %return elf.in_stream.seekTo(elf.section_header_offset); + + elf.section_headers = %return elf.allocator.alloc(SectionHeader, sh_entry_count); + %defer elf.allocator.free(SectionHeader, elf.section_headers); + + if (elf.is_64) { + if (sh_entry_size != 64) return error.InvalidFormat; + + for (elf.section_headers) |*section| { + section.name = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.sh_type = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.flags = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.addr = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.size = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.link = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.info = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.addr_align = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.ent_size = %return elf.in_stream.readInt(elf.is_big_endian, u64); + } + } else { + if (sh_entry_size != 40) return error.InvalidFormat; + + for (elf.section_headers) |*section| { + // TODO (multiple occurences) allow implicit cast from %u32 -> %u64 ? + section.name = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.sh_type = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.flags = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.link = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.info = %return elf.in_stream.readInt(elf.is_big_endian, u32); + section.addr_align = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.ent_size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + } + } + + for (elf.section_headers) |*section| { + if (section.sh_type != SHT_NOBITS) { + const file_end_offset = %return math.addOverflow(u64, + section.offset, section.size); + if (stream_end < file_end_offset) return error.InvalidFormat; + } + } + + elf.string_section = &elf.section_headers[elf.string_section_index]; + if (elf.string_section.sh_type != SHT_STRTAB) { + // not a string table + return error.InvalidFormat; + } + } + + pub fn close(elf: &Elf) -> %void { + elf.allocator.free(SectionHeader, elf.section_headers); + if (elf.auto_close_stream) %return elf.in_stream.close(); + } + + pub fn findSection(elf: &Elf, name: []u8) -> %?&SectionHeader { + for (elf.section_headers) |*section| { + if (section.sh_type == SHT_NULL) continue; + + const name_offset = elf.string_section.offset + section.name; + %return elf.in_stream.seekTo(name_offset); + + for (name) |expected_c| { + const target_c = %return elf.in_stream.readByte(); + if (target_c == 0 || expected_c != target_c) goto next_section; + } + + const null_byte = %return elf.in_stream.readByte(); + if (null_byte == 0) return (?&SectionHeader)(section); + + next_section: + } + + const null_sh: ?&SectionHeader = null; + return null_sh; + } + + pub fn seekToSection(elf: &Elf, section: &SectionHeader) -> %void { + %return elf.in_stream.seekTo(section.offset); + } +} diff --git a/std/endian.zig b/std/endian.zig new file mode 100644 index 0000000000..dbf518d801 --- /dev/null +++ b/std/endian.zig @@ -0,0 +1,21 @@ +pub inline fn swapIfLe(inline T: type, x: T) -> T { + swapIf(false, T, x) +} + +pub inline fn swapIfBe(inline T: type, x: T) -> T { + swapIf(true, T, x) +} + +pub inline fn swapIf(is_be: bool, inline T: type, x: T) -> T { + if (@compileVar("is_big_endian") == is_be) swap(T, x) else x +} + +pub fn swap(inline T: type, x: T) -> T { + const x_slice = ([]u8)((&const x)[0...1]); + var result: T = undefined; + const result_slice = ([]u8)((&result)[0...1]); + for (result_slice) |*b, i| { + *b = x_slice[@sizeOf(T) - i - 1]; + } + return result; +} diff --git a/std/io.zig b/std/io.zig index 3897469834..3c8a95dbef 100644 --- a/std/io.zig +++ b/std/io.zig @@ -1,6 +1,9 @@ const linux = @import("linux.zig"); const errno = @import("errno.zig"); const math = @import("math.zig"); +const endian = @import("endian.zig"); +const debug = @import("debug.zig"); +const assert = debug.assert; pub const stdin_fileno = 0; pub const stdout_fileno = 1; @@ -8,6 +11,7 @@ pub const stderr_fileno = 2; pub var stdin = InStream { .fd = stdin_fileno, + .offset = 0, }; pub var stdout = OutStream { @@ -33,6 +37,8 @@ pub error Unexpected; pub error DiskQuota; pub error FileTooBig; +// TODO hide interrupts at this layer by retrying. Users can use the linux specific APIs if they +// want to handle interrupts. pub error SigInterrupt; pub error Io; pub error NoSpaceLeft; @@ -48,6 +54,8 @@ pub error NameTooLong; pub error NoDevice; pub error PathNotFound; pub error NoMem; +pub error Unseekable; +pub error Eof; const buffer_size = 4 * 1024; const max_u64_base10_digits = 20; @@ -137,14 +145,18 @@ pub struct OutStream { } } +// TODO created a BufferedInStream struct and move some of this code there +// BufferedInStream API goes on top of minimal InStream API. pub struct InStream { fd: i32, + offset: usize, - pub fn open(path: []u8) -> %InStream { - const fd = linux.open(path, linux.O_LARGEFILE|linux.O_RDONLY, 0); - const fd_err = linux.getErrno(fd); - if (fd_err > 0) { - return switch (fd_err) { + /// Call close to clean up. + pub fn open(is: &InStream, path: []const u8) -> %void { + const result = linux.open(path, linux.O_LARGEFILE|linux.O_RDONLY, 0); + const err = linux.getErrno(result); + if (err > 0) { + return switch (err) { errno.EFAULT => unreachable{}, errno.EINVAL => unreachable{}, errno.EACCES => error.BadPerm, @@ -164,24 +176,8 @@ pub struct InStream { else => error.Unexpected, } } - - return InStream { .fd = i32(fd), }; - } - - pub fn read(is: &InStream, buf: []u8) -> %usize { - const amt_read = linux.read(is.fd, &buf[0], buf.len); - const read_err = linux.getErrno(amt_read); - if (read_err > 0) { - return switch (read_err) { - errno.EINVAL => unreachable{}, - errno.EFAULT => unreachable{}, - errno.EBADF => error.BadFd, - errno.EINTR => error.SigInterrupt, - errno.EIO => error.Io, - else => error.Unexpected, - } - } - return amt_read; + is.fd = i32(result); + is.offset = 0; } pub fn close(is: &InStream) -> %void { @@ -196,6 +192,95 @@ pub struct InStream { } } } + + /// Returns the number of bytes read. If the number read is smaller than buf.len, then + /// the stream reached End Of File. + pub fn read(is: &InStream, buf: []u8) -> %usize { + switch (@compileVar("os")) { + linux => { + while (true) { + const amt_read = linux.pread(is.fd, buf.ptr, buf.len, is.offset); + const read_err = linux.getErrno(amt_read); + if (read_err > 0) { + switch (read_err) { + errno.EINTR => continue, + errno.EINVAL => unreachable{}, + errno.EFAULT => unreachable{}, + errno.EBADF => return error.BadFd, + errno.EIO => return error.Io, + else => return error.Unexpected, + } + } + is.offset += amt_read; + return amt_read; + } + }, + else => @compileErr("unsupported OS"), + } + } + + pub fn readNoEof(is: &InStream, buf: []u8) -> %void { + const amt_read = %return is.read(buf); + if (amt_read < buf.len) return error.Eof; + } + + pub fn readByte(is: &InStream) -> %u8 { + var result: [1]u8 = undefined; + %return is.readNoEof(result); + return result[0]; + } + + pub inline fn readIntLe(is: &InStream, inline T: type) -> %T { + is.readInt(false, T) + } + + pub inline fn readIntBe(is: &InStream, inline T: type) -> %T { + is.readInt(true, T) + } + + pub inline fn readInt(is: &InStream, is_be: bool, inline T: type) -> %T { + var result: T = undefined; + const result_slice = ([]u8)((&result)[0...1]); + %return is.readNoEof(result_slice); + return endian.swapIf(!is_be, T, result); + } + + pub inline fn readVarInt(is: &InStream, is_be: bool, inline T: type, size: usize) -> %T { + var result: T = zeroes; + const result_slice = ([]u8)((&result)[0...1]); + const padding = @sizeOf(T) - size; + {var i: usize = 0; while (i < size; i += 1) { + const index = if (is_be == @compileVar("is_big_endian")) { + padding + i + } else { + result_slice.len - i - 1 - padding + }; + result_slice[index] = %return is.readByte(); + }} + return result; + } + + pub fn seekForward(is: &InStream, amount: usize) -> %void { + is.offset += amount; + } + + pub fn seekTo(is: &InStream, pos: usize) -> %void { + is.offset = pos; + } + + pub fn endPos(is: &InStream) -> %usize { + var stat: linux.stat = undefined; + const err = linux.getErrno(linux.fstat(is.fd, &stat)); + if (err > 0) { + return switch (err) { + errno.EBADF => error.BadFd, + errno.ENOMEM => error.NoMem, + else => error.Unexpected, + } + } + + return usize(stat.size); + } } pub fn parseUnsigned(inline T: type, buf: []u8, radix: u8) -> %T { @@ -267,3 +352,12 @@ fn parseU64DigitTooBig() { }; unreachable{}; } + +pub fn openSelfExe(stream: &InStream) -> %void { + switch (@compileVar("os")) { + linux => { + %return stream.open("/proc/self/exe"); + }, + else => @compileErr("unsupported os"), + } +} diff --git a/std/linux.zig b/std/linux.zig index 330d3db22a..d02b602ca3 100644 --- a/std/linux.zig +++ b/std/linux.zig @@ -76,6 +76,10 @@ pub const O_PATH = arch.O_PATH; pub const O_TMPFILE = arch.O_TMPFILE; pub const O_NDELAY = arch.O_NDELAY; +pub const SEEK_SET = 0; +pub const SEEK_CUR = 1; +pub const SEEK_END = 2; + const SIG_BLOCK = 0; const SIG_UNBLOCK = 1; const SIG_SETMASK = 2; @@ -226,7 +230,9 @@ pub fn getErrno(r: usize) -> usize { if (signed_r > -4096 && signed_r < 0) usize(-signed_r) else 0 } -pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize) -> usize { +pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize) + -> usize +{ arch.syscall6(arch.SYS_mmap, usize(address), length, prot, flags, usize(fd), offset) } @@ -238,29 +244,49 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize { arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count) } +pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) -> usize { + arch.syscall4(arch.SYS_pread, usize(fd), usize(buf), count, offset) +} + pub fn write(fd: i32, buf: &const u8, count: usize) -> usize { arch.syscall3(arch.SYS_write, usize(fd), usize(buf), count) } -pub fn open(path: []u8, flags: usize, perm: usize) -> usize { - var buf: [path.len + 1]u8 = undefined; - @memcpy(&buf[0], &path[0], path.len); - buf[path.len] = 0; - arch.syscall3(arch.SYS_open, usize(&buf[0]), flags, perm) +pub fn pwrite(fd: i32, buf: &const u8, count: usize, offset: usize) -> usize { + arch.syscall4(arch.SYS_pwrite, usize(fd), usize(buf), count, offset) } -pub fn create(path: []u8, perm: usize) -> usize { - var buf: [path.len + 1]u8 = undefined; - @memcpy(&buf[0], &path[0], path.len); - buf[path.len] = 0; - arch.syscall2(arch.SYS_creat, usize(&buf[0]), perm) +pub fn open_c(path: &const u8, flags: usize, perm: usize) -> usize { + arch.syscall3(arch.SYS_open, usize(path), flags, perm) } -pub fn openat(dirfd: i32, path: []u8, flags: usize, mode: usize) -> usize { +pub fn open(path: []const u8, flags: usize, perm: usize) -> usize { var buf: [path.len + 1]u8 = undefined; @memcpy(&buf[0], &path[0], path.len); buf[path.len] = 0; - arch.syscall4(arch.SYS_openat, usize(dirfd), usize(&buf[0]), flags, mode) + return open_c(buf.ptr, flags, perm); +} + +pub fn create_c(path: &const u8, perm: usize) -> usize { + arch.syscall2(arch.SYS_creat, usize(path), perm) +} + +pub fn create(path: []const u8, perm: usize) -> usize { + var buf: [path.len + 1]u8 = undefined; + @memcpy(&buf[0], &path[0], path.len); + buf[path.len] = 0; + return create_c(buf.ptr, perm); +} + +pub fn openat_c(dirfd: i32, path: &const u8, flags: usize, mode: usize) -> usize { + arch.syscall4(arch.SYS_openat, usize(dirfd), usize(path), flags, mode) +} + +pub fn openat(dirfd: i32, path: []const u8, flags: usize, mode: usize) -> usize { + var buf: [path.len + 1]u8 = undefined; + @memcpy(&buf[0], &path[0], path.len); + buf[path.len] = 0; + return openat_c(dirfd, buf.ptr, flags, mode); } pub fn close(fd: i32) -> usize { @@ -457,3 +483,10 @@ pub fn accept4(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t, flags: // } // return ifr.ifr_ifindex; // } + +pub const stat = arch.stat; +pub const timespec = arch.timespec; + +pub fn fstat(fd: i32, stat_buf: &stat) -> usize { + arch.syscall2(arch.SYS_fstat, usize(fd), usize(stat_buf)) +} diff --git a/std/linux_x86_64.zig b/std/linux_x86_64.zig index b2f48ceb61..3b42bfb461 100644 --- a/std/linux_x86_64.zig +++ b/std/linux_x86_64.zig @@ -19,8 +19,8 @@ pub const SYS_rt_sigaction = 13; pub const SYS_rt_sigprocmask = 14; pub const SYS_rt_sigreturn = 15; pub const SYS_ioctl = 16; -pub const SYS_pread64 = 17; -pub const SYS_pwrite64 = 18; +pub const SYS_pread = 17; +pub const SYS_pwrite = 18; pub const SYS_readv = 19; pub const SYS_writev = 20; pub const SYS_access = 21; @@ -453,3 +453,28 @@ export struct msghdr { __pad2: socklen_t, msg_flags: i32, } + +export struct stat { + dev: u64, + ino: u64, + nlink: usize, + + mode: u32, + uid: u32, + gid: u32, + __pad0: u32, + rdev: u64, + size: i64, + blksize: isize, + blocks: i64, + + atim: timespec, + mtim: timespec, + ctim: timespec, + __unused: [3]isize, +} + +export struct timespec { + tv_sec: isize, + tv_nsec: isize, +} diff --git a/std/net.zig b/std/net.zig index 8adb6e8882..38a28ea986 100644 --- a/std/net.zig +++ b/std/net.zig @@ -1,6 +1,7 @@ const linux = @import("linux.zig"); const errno = @import("errno.zig"); const assert = @import("debug.zig").assert; +const endian = @import("endian.zig"); pub error SigInterrupt; pub error Unexpected; @@ -99,14 +100,14 @@ pub fn connectAddr(addr: &Address, port: u16) -> %Connection { const connect_ret = if (addr.family == linux.AF_INET) { var os_addr: linux.sockaddr_in = undefined; os_addr.family = addr.family; - os_addr.port = swapIfLittleEndian(u16, port); + os_addr.port = endian.swapIfLe(u16, port); @memcpy((&u8)(&os_addr.addr), &addr.addr[0], 4); @memset(&os_addr.zero, 0, @sizeOf(@typeOf(os_addr.zero))); linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeOf(linux.sockaddr_in)) } else if (addr.family == linux.AF_INET6) { var os_addr: linux.sockaddr_in6 = undefined; os_addr.family = addr.family; - os_addr.port = swapIfLittleEndian(u16, port); + os_addr.port = endian.swapIfLe(u16, port); os_addr.flowinfo = 0; os_addr.scope_id = addr.scope_id; @memcpy(&os_addr.addr[0], &addr.addr[0], 16); @@ -319,7 +320,7 @@ fn parseIp4(buf: []const u8) -> %u32 { #attribute("test") fn testParseIp4() { - assert(%%parseIp4("127.0.0.1") == swapIfLittleEndian(u32, 0x7f000001)); + assert(%%parseIp4("127.0.0.1") == endian.swapIfLe(u32, 0x7f000001)); switch (parseIp4("256.0.0.1")) { Overflow => {}, else => unreachable {}, } switch (parseIp4("x.0.0.1")) { InvalidChar => {}, else => unreachable {}, } switch (parseIp4("127.0.0.1.1")) { JunkAtEnd => {}, else => unreachable {}, } @@ -351,17 +352,3 @@ fn testLookupSimpleIp() { assert(addr.addr[3] == 1); } } - -fn swapIfLittleEndian(inline T: type, x: T) -> T { - if (@compileVar("is_big_endian")) x else endianSwap(T, x) -} - -fn endianSwap(inline T: type, x: T) -> T { - const x_slice = ([]u8)((&const x)[0...1]); - var result: T = undefined; - const result_slice = ([]u8)((&result)[0...1]); - for (result_slice) |*b, i| { - *b = x_slice[@sizeOf(T) - i - 1]; - } - return result; -} diff --git a/std/os.zig b/std/os.zig index a60cd6a575..869054e012 100644 --- a/std/os.zig +++ b/std/os.zig @@ -4,7 +4,7 @@ const errno = @import("errno.zig"); pub error SigInterrupt; pub error Unexpected; -pub fn get_random_bytes(buf: []u8) -> %void { +pub fn getRandomBytes(buf: []u8) -> %void { switch (@compileVar("os")) { linux => { const ret = linux.getrandom(buf.ptr, buf.len, 0); @@ -18,14 +18,18 @@ pub fn get_random_bytes(buf: []u8) -> %void { } } }, - else => @compile_err("unsupported os"), + else => @compileErr("unsupported os"), } } #attribute("cold") pub fn abort() -> unreachable { - linux.raise(linux.SIGABRT); - linux.raise(linux.SIGKILL); - while (true) {} + switch (@compileVar("os")) { + linux => { + linux.raise(linux.SIGABRT); + linux.raise(linux.SIGKILL); + while (true) {} + }, + else => @compileErr("unsupported os"), + } } - diff --git a/std/rand.zig b/std/rand.zig index d2ce52ce27..bdb099e98f 100644 --- a/std/rand.zig +++ b/std/rand.zig @@ -87,7 +87,7 @@ pub struct Rand { } else if (T == f64) { 9007199254740992 } else { - @compile_err("unknown floating point type" ++ @typeName(T)) + @compileErr("unknown floating point type" ++ @typeName(T)) }; return T(r.rangeUnsigned(int_type, 0, precision)) / T(precision); }