| author | |
| committer | |
| log | b1d86db7b45c57b2a9d48655738bce8d77327438 |
| tree | 02294dfdfb662d582546bc36c878a42eba5f5343 |
| parent | 8e6a62ba10326e48eaefd40f89c9452d92f39c9d |
dwarf: fixup unchecked .eh_frame CIE offset subtraction3 files changed, 361 insertions(+), 359 deletions(-)
lib/std/debug.zig+1-1| ... | ... | @@ -648,7 +648,7 @@ pub const StackIterator = struct { |
| 648 | 648 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding |
| 649 | 649 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. |
| 650 | 650 | if (module.unwind_info) |unwind_info| { |
| 651 | if (macho.unwindFrame(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| { | |
| 651 | if (DW.unwindFrameMachO(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| { | |
| 652 | 652 | return return_address; |
| 653 | 653 | } else |err| { |
| 654 | 654 | if (err != error.RequiresDWARFUnwind) return err; |
lib/std/dwarf.zig+360-1| ... | ... | @@ -1841,6 +1841,365 @@ pub const DwarfInfo = struct { |
| 1841 | 1841 | } |
| 1842 | 1842 | }; |
| 1843 | 1843 | |
| 1844 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | |
| 1845 | fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | |
| 1846 | return switch (unwind_reg_number) { | |
| 1847 | 1 => 3, // RBX | |
| 1848 | 2 => 12, // R12 | |
| 1849 | 3 => 13, // R13 | |
| 1850 | 4 => 14, // R14 | |
| 1851 | 5 => 15, // R15 | |
| 1852 | 6 => 6, // RBP | |
| 1853 | else => error.InvalidUnwindRegisterNumber, | |
| 1854 | }; | |
| 1855 | } | |
| 1856 | ||
| 1857 | const macho = std.macho; | |
| 1858 | ||
| 1859 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | |
| 1860 | /// If the compact encoding can't encode a way to unwind a frame, it will | |
| 1861 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | |
| 1862 | pub fn unwindFrameMachO(context: *UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize { | |
| 1863 | const header = mem.bytesAsValue( | |
| 1864 | macho.unwind_info_section_header, | |
| 1865 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | |
| 1866 | ); | |
| 1867 | const indices = mem.bytesAsSlice( | |
| 1868 | macho.unwind_info_section_header_index_entry, | |
| 1869 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | |
| 1870 | ); | |
| 1871 | if (indices.len == 0) return error.MissingUnwindInfo; | |
| 1872 | ||
| 1873 | const mapped_pc = context.pc - module_base_address; | |
| 1874 | const second_level_index = blk: { | |
| 1875 | var left: usize = 0; | |
| 1876 | var len: usize = indices.len; | |
| 1877 | ||
| 1878 | while (len > 1) { | |
| 1879 | const mid = left + len / 2; | |
| 1880 | const offset = indices[mid].functionOffset; | |
| 1881 | if (mapped_pc < offset) { | |
| 1882 | len /= 2; | |
| 1883 | } else { | |
| 1884 | left = mid; | |
| 1885 | if (mapped_pc == offset) break; | |
| 1886 | len -= len / 2; | |
| 1887 | } | |
| 1888 | } | |
| 1889 | ||
| 1890 | // Last index is a sentinel containing the highest address as its functionOffset | |
| 1891 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | |
| 1892 | break :blk &indices[left]; | |
| 1893 | }; | |
| 1894 | ||
| 1895 | const common_encodings = mem.bytesAsSlice( | |
| 1896 | macho.compact_unwind_encoding_t, | |
| 1897 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | |
| 1898 | ); | |
| 1899 | ||
| 1900 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | |
| 1901 | const kind = mem.bytesAsValue( | |
| 1902 | macho.UNWIND_SECOND_LEVEL, | |
| 1903 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | |
| 1904 | ); | |
| 1905 | ||
| 1906 | const entry: struct { | |
| 1907 | function_offset: usize, | |
| 1908 | raw_encoding: u32, | |
| 1909 | } = switch (kind.*) { | |
| 1910 | .REGULAR => blk: { | |
| 1911 | const page_header = mem.bytesAsValue( | |
| 1912 | macho.unwind_info_regular_second_level_page_header, | |
| 1913 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | |
| 1914 | ); | |
| 1915 | ||
| 1916 | const entries = mem.bytesAsSlice( | |
| 1917 | macho.unwind_info_regular_second_level_entry, | |
| 1918 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | |
| 1919 | ); | |
| 1920 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 1921 | ||
| 1922 | var left: usize = 0; | |
| 1923 | var len: usize = entries.len; | |
| 1924 | while (len > 1) { | |
| 1925 | const mid = left + len / 2; | |
| 1926 | const offset = entries[mid].functionOffset; | |
| 1927 | if (mapped_pc < offset) { | |
| 1928 | len /= 2; | |
| 1929 | } else { | |
| 1930 | left = mid; | |
| 1931 | if (mapped_pc == offset) break; | |
| 1932 | len -= len / 2; | |
| 1933 | } | |
| 1934 | } | |
| 1935 | ||
| 1936 | break :blk .{ | |
| 1937 | .function_offset = entries[left].functionOffset, | |
| 1938 | .raw_encoding = entries[left].encoding, | |
| 1939 | }; | |
| 1940 | }, | |
| 1941 | .COMPRESSED => blk: { | |
| 1942 | const page_header = mem.bytesAsValue( | |
| 1943 | macho.unwind_info_compressed_second_level_page_header, | |
| 1944 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | |
| 1945 | ); | |
| 1946 | ||
| 1947 | const entries = mem.bytesAsSlice( | |
| 1948 | macho.UnwindInfoCompressedEntry, | |
| 1949 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | |
| 1950 | ); | |
| 1951 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 1952 | ||
| 1953 | var left: usize = 0; | |
| 1954 | var len: usize = entries.len; | |
| 1955 | while (len > 1) { | |
| 1956 | const mid = left + len / 2; | |
| 1957 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | |
| 1958 | if (mapped_pc < offset) { | |
| 1959 | len /= 2; | |
| 1960 | } else { | |
| 1961 | left = mid; | |
| 1962 | if (mapped_pc == offset) break; | |
| 1963 | len -= len / 2; | |
| 1964 | } | |
| 1965 | } | |
| 1966 | ||
| 1967 | const entry = entries[left]; | |
| 1968 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | |
| 1969 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | |
| 1970 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | |
| 1971 | break :blk .{ | |
| 1972 | .function_offset = function_offset, | |
| 1973 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 1974 | }; | |
| 1975 | } else { | |
| 1976 | const local_index = try std.math.sub( | |
| 1977 | u8, | |
| 1978 | entry.encodingIndex, | |
| 1979 | std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | |
| 1980 | ); | |
| 1981 | const local_encodings = mem.bytesAsSlice( | |
| 1982 | macho.compact_unwind_encoding_t, | |
| 1983 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | |
| 1984 | ); | |
| 1985 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | |
| 1986 | break :blk .{ | |
| 1987 | .function_offset = function_offset, | |
| 1988 | .raw_encoding = local_encodings[local_index], | |
| 1989 | }; | |
| 1990 | } | |
| 1991 | }, | |
| 1992 | else => return error.InvalidUnwindInfo, | |
| 1993 | }; | |
| 1994 | ||
| 1995 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | |
| 1996 | const reg_context = abi.RegisterContext{ | |
| 1997 | .eh_frame = false, | |
| 1998 | .is_macho = true, | |
| 1999 | }; | |
| 2000 | ||
| 2001 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 2002 | const new_ip = switch (builtin.cpu.arch) { | |
| 2003 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 2004 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 2005 | .RBP_FRAME => blk: { | |
| 2006 | const regs: [5]u3 = .{ | |
| 2007 | encoding.value.x86_64.frame.reg0, | |
| 2008 | encoding.value.x86_64.frame.reg1, | |
| 2009 | encoding.value.x86_64.frame.reg2, | |
| 2010 | encoding.value.x86_64.frame.reg3, | |
| 2011 | encoding.value.x86_64.frame.reg4, | |
| 2012 | }; | |
| 2013 | ||
| 2014 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | |
| 2015 | var max_reg: usize = 0; | |
| 2016 | inline for (regs, 0..) |reg, i| { | |
| 2017 | if (reg > 0) max_reg = i; | |
| 2018 | } | |
| 2019 | ||
| 2020 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | |
| 2021 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 2022 | ||
| 2023 | // Verify the stack range we're about to read register values from | |
| 2024 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo; | |
| 2025 | ||
| 2026 | const ip_ptr = fp + @sizeOf(usize); | |
| 2027 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2028 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 2029 | ||
| 2030 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 2031 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2032 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2033 | ||
| 2034 | for (regs, 0..) |reg, i| { | |
| 2035 | if (reg == 0) continue; | |
| 2036 | const addr = fp - frame_offset + i * @sizeOf(usize); | |
| 2037 | const reg_number = try compactUnwindToDwarfRegNumber(reg); | |
| 2038 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 2039 | } | |
| 2040 | ||
| 2041 | break :blk new_ip; | |
| 2042 | }, | |
| 2043 | .STACK_IMMD, | |
| 2044 | .STACK_IND, | |
| 2045 | => blk: { | |
| 2046 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | |
| 2047 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | |
| 2048 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | |
| 2049 | else stack_size: { | |
| 2050 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 2051 | const sub_offset_addr = | |
| 2052 | module_base_address + | |
| 2053 | entry.function_offset + | |
| 2054 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | |
| 2055 | if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo; | |
| 2056 | ||
| 2057 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 2058 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 2059 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | |
| 2060 | }; | |
| 2061 | ||
| 2062 | // Decode the Lehmer-coded sequence of registers. | |
| 2063 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 2064 | ||
| 2065 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 2066 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 2067 | // the time the digit was added. | |
| 2068 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | |
| 2069 | const ip_ptr = if (reg_count > 0) reg_blk: { | |
| 2070 | var digits: [6]u3 = undefined; | |
| 2071 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | |
| 2072 | var base: usize = 2; | |
| 2073 | for (0..reg_count) |i| { | |
| 2074 | const div = accumulator / base; | |
| 2075 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 2076 | accumulator = div; | |
| 2077 | base += 1; | |
| 2078 | } | |
| 2079 | ||
| 2080 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | |
| 2081 | var registers: [reg_numbers.len]u3 = undefined; | |
| 2082 | var used_indices = [_]bool{false} ** reg_numbers.len; | |
| 2083 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 2084 | var unused_count: u8 = 0; | |
| 2085 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 2086 | if (!used) { | |
| 2087 | if (target_unused_index == unused_count) break index; | |
| 2088 | unused_count += 1; | |
| 2089 | } | |
| 2090 | } else unreachable; | |
| 2091 | ||
| 2092 | registers[i] = reg_numbers[unused_index]; | |
| 2093 | used_indices[unused_index] = true; | |
| 2094 | } | |
| 2095 | ||
| 2096 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 2097 | if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo; | |
| 2098 | for (0..reg_count) |i| { | |
| 2099 | const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); | |
| 2100 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2101 | reg_addr += @sizeOf(usize); | |
| 2102 | } | |
| 2103 | ||
| 2104 | break :reg_blk reg_addr; | |
| 2105 | } else sp + stack_size - @sizeOf(usize); | |
| 2106 | ||
| 2107 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2108 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 2109 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | |
| 2110 | ||
| 2111 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2112 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2113 | ||
| 2114 | break :blk new_ip; | |
| 2115 | }, | |
| 2116 | .DWARF => { | |
| 2117 | return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | |
| 2118 | }, | |
| 2119 | }, | |
| 2120 | .aarch64 => switch (encoding.mode.arm64) { | |
| 2121 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 2122 | .FRAMELESS => blk: { | |
| 2123 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | |
| 2124 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 2125 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | |
| 2126 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | |
| 2127 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2128 | break :blk new_ip; | |
| 2129 | }, | |
| 2130 | .DWARF => { | |
| 2131 | return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | |
| 2132 | }, | |
| 2133 | .FRAME => blk: { | |
| 2134 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | |
| 2135 | const new_sp = fp + 16; | |
| 2136 | const ip_ptr = fp + @sizeOf(usize); | |
| 2137 | ||
| 2138 | const num_restored_pairs: usize = | |
| 2139 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | |
| 2140 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | |
| 2141 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | |
| 2142 | ||
| 2143 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo; | |
| 2144 | ||
| 2145 | var reg_addr = fp - @sizeOf(usize); | |
| 2146 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | |
| 2147 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | |
| 2148 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2149 | reg_addr += @sizeOf(usize); | |
| 2150 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2151 | reg_addr += @sizeOf(usize); | |
| 2152 | } | |
| 2153 | } | |
| 2154 | ||
| 2155 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | |
| 2156 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | |
| 2157 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 2158 | @memcpy( | |
| 2159 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | |
| 2160 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 2161 | ); | |
| 2162 | reg_addr += @sizeOf(usize); | |
| 2163 | @memcpy( | |
| 2164 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | |
| 2165 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 2166 | ); | |
| 2167 | reg_addr += @sizeOf(usize); | |
| 2168 | } | |
| 2169 | } | |
| 2170 | ||
| 2171 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2172 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 2173 | ||
| 2174 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 2175 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2176 | ||
| 2177 | break :blk new_ip; | |
| 2178 | }, | |
| 2179 | }, | |
| 2180 | else => return error.UnimplementedArch, | |
| 2181 | }; | |
| 2182 | ||
| 2183 | context.pc = abi.stripInstructionPtrAuthCode(new_ip); | |
| 2184 | if (context.pc > 0) context.pc -= 1; | |
| 2185 | return new_ip; | |
| 2186 | } | |
| 2187 | ||
| 2188 | fn unwindFrameMachODwarf(context: *UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize { | |
| 2189 | var di = DwarfInfo{ | |
| 2190 | .endian = builtin.cpu.arch.endian(), | |
| 2191 | .is_macho = true, | |
| 2192 | }; | |
| 2193 | defer di.deinit(context.allocator); | |
| 2194 | ||
| 2195 | di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{ | |
| 2196 | .data = eh_frame, | |
| 2197 | .owned = false, | |
| 2198 | }; | |
| 2199 | ||
| 2200 | return di.unwindFrame(context, fde_offset); | |
| 2201 | } | |
| 2202 | ||
| 1844 | 2203 | pub const UnwindContext = struct { |
| 1845 | 2204 | allocator: mem.Allocator, |
| 1846 | 2205 | cfa: ?usize, |
| ... | ... | @@ -2166,7 +2525,7 @@ pub const EntryHeader = struct { |
| 2166 | 2525 | .is_64 = is_64, |
| 2167 | 2526 | .type = if (id == cie_id) .{ .cie = {} } else .{ |
| 2168 | 2527 | .fde = switch (dwarf_section) { |
| 2169 | .eh_frame => stream.pos - id_len - id, | |
| 2528 | .eh_frame => try std.math.sub(u64, stream.pos - id_len, id), | |
| 2170 | 2529 | .debug_frame => id, |
| 2171 | 2530 | else => unreachable, |
| 2172 | 2531 | }, |
lib/std/macho.zig-357| ... | ... | @@ -2125,360 +2125,3 @@ pub const CompactUnwindEncoding = packed struct(u32) { |
| 2125 | 2125 | has_lsda: u1, |
| 2126 | 2126 | start: u1, |
| 2127 | 2127 | }; |
| 2128 | ||
| 2129 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | |
| 2130 | fn dwarfRegNumber(unwind_reg_number: u3) !u8 { | |
| 2131 | return switch (unwind_reg_number) { | |
| 2132 | 1 => 3, // RBX | |
| 2133 | 2 => 12, // R12 | |
| 2134 | 3 => 13, // R13 | |
| 2135 | 4 => 14, // R14 | |
| 2136 | 5 => 15, // R15 | |
| 2137 | 6 => 6, // RBP | |
| 2138 | else => error.InvalidUnwindRegisterNumber, | |
| 2139 | }; | |
| 2140 | } | |
| 2141 | ||
| 2142 | const dwarf = std.dwarf; | |
| 2143 | const abi = dwarf.abi; | |
| 2144 | ||
| 2145 | pub fn unwindFrame(context: *dwarf.UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize { | |
| 2146 | const header = mem.bytesAsValue( | |
| 2147 | unwind_info_section_header, | |
| 2148 | unwind_info[0..@sizeOf(unwind_info_section_header)], | |
| 2149 | ); | |
| 2150 | const indices = mem.bytesAsSlice( | |
| 2151 | unwind_info_section_header_index_entry, | |
| 2152 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(unwind_info_section_header_index_entry)], | |
| 2153 | ); | |
| 2154 | if (indices.len == 0) return error.MissingUnwindInfo; | |
| 2155 | ||
| 2156 | const mapped_pc = context.pc - module_base_address; | |
| 2157 | const second_level_index = blk: { | |
| 2158 | var left: usize = 0; | |
| 2159 | var len: usize = indices.len; | |
| 2160 | ||
| 2161 | while (len > 1) { | |
| 2162 | const mid = left + len / 2; | |
| 2163 | const offset = indices[mid].functionOffset; | |
| 2164 | if (mapped_pc < offset) { | |
| 2165 | len /= 2; | |
| 2166 | } else { | |
| 2167 | left = mid; | |
| 2168 | if (mapped_pc == offset) break; | |
| 2169 | len -= len / 2; | |
| 2170 | } | |
| 2171 | } | |
| 2172 | ||
| 2173 | // Last index is a sentinel containing the highest address as its functionOffset | |
| 2174 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | |
| 2175 | break :blk &indices[left]; | |
| 2176 | }; | |
| 2177 | ||
| 2178 | const common_encodings = mem.bytesAsSlice( | |
| 2179 | compact_unwind_encoding_t, | |
| 2180 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(compact_unwind_encoding_t)], | |
| 2181 | ); | |
| 2182 | ||
| 2183 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | |
| 2184 | const kind = mem.bytesAsValue( | |
| 2185 | UNWIND_SECOND_LEVEL, | |
| 2186 | unwind_info[start_offset..][0..@sizeOf(UNWIND_SECOND_LEVEL)], | |
| 2187 | ); | |
| 2188 | ||
| 2189 | const entry: struct { | |
| 2190 | function_offset: usize, | |
| 2191 | raw_encoding: u32, | |
| 2192 | } = switch (kind.*) { | |
| 2193 | .REGULAR => blk: { | |
| 2194 | const page_header = mem.bytesAsValue( | |
| 2195 | unwind_info_regular_second_level_page_header, | |
| 2196 | unwind_info[start_offset..][0..@sizeOf(unwind_info_regular_second_level_page_header)], | |
| 2197 | ); | |
| 2198 | ||
| 2199 | const entries = mem.bytesAsSlice( | |
| 2200 | unwind_info_regular_second_level_entry, | |
| 2201 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(unwind_info_regular_second_level_entry)], | |
| 2202 | ); | |
| 2203 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 2204 | ||
| 2205 | var left: usize = 0; | |
| 2206 | var len: usize = entries.len; | |
| 2207 | while (len > 1) { | |
| 2208 | const mid = left + len / 2; | |
| 2209 | const offset = entries[mid].functionOffset; | |
| 2210 | if (mapped_pc < offset) { | |
| 2211 | len /= 2; | |
| 2212 | } else { | |
| 2213 | left = mid; | |
| 2214 | if (mapped_pc == offset) break; | |
| 2215 | len -= len / 2; | |
| 2216 | } | |
| 2217 | } | |
| 2218 | ||
| 2219 | break :blk .{ | |
| 2220 | .function_offset = entries[left].functionOffset, | |
| 2221 | .raw_encoding = entries[left].encoding, | |
| 2222 | }; | |
| 2223 | }, | |
| 2224 | .COMPRESSED => blk: { | |
| 2225 | const page_header = mem.bytesAsValue( | |
| 2226 | unwind_info_compressed_second_level_page_header, | |
| 2227 | unwind_info[start_offset..][0..@sizeOf(unwind_info_compressed_second_level_page_header)], | |
| 2228 | ); | |
| 2229 | ||
| 2230 | const entries = mem.bytesAsSlice( | |
| 2231 | UnwindInfoCompressedEntry, | |
| 2232 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(UnwindInfoCompressedEntry)], | |
| 2233 | ); | |
| 2234 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 2235 | ||
| 2236 | var left: usize = 0; | |
| 2237 | var len: usize = entries.len; | |
| 2238 | while (len > 1) { | |
| 2239 | const mid = left + len / 2; | |
| 2240 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | |
| 2241 | if (mapped_pc < offset) { | |
| 2242 | len /= 2; | |
| 2243 | } else { | |
| 2244 | left = mid; | |
| 2245 | if (mapped_pc == offset) break; | |
| 2246 | len -= len / 2; | |
| 2247 | } | |
| 2248 | } | |
| 2249 | ||
| 2250 | const entry = entries[left]; | |
| 2251 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | |
| 2252 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | |
| 2253 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | |
| 2254 | break :blk .{ | |
| 2255 | .function_offset = function_offset, | |
| 2256 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 2257 | }; | |
| 2258 | } else { | |
| 2259 | const local_index = try std.math.sub( | |
| 2260 | u8, | |
| 2261 | entry.encodingIndex, | |
| 2262 | std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | |
| 2263 | ); | |
| 2264 | const local_encodings = mem.bytesAsSlice( | |
| 2265 | compact_unwind_encoding_t, | |
| 2266 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(compact_unwind_encoding_t)], | |
| 2267 | ); | |
| 2268 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | |
| 2269 | break :blk .{ | |
| 2270 | .function_offset = function_offset, | |
| 2271 | .raw_encoding = local_encodings[local_index], | |
| 2272 | }; | |
| 2273 | } | |
| 2274 | }, | |
| 2275 | else => return error.InvalidUnwindInfo, | |
| 2276 | }; | |
| 2277 | ||
| 2278 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | |
| 2279 | const reg_context = dwarf.abi.RegisterContext{ | |
| 2280 | .eh_frame = false, | |
| 2281 | .is_macho = true, | |
| 2282 | }; | |
| 2283 | ||
| 2284 | const encoding: CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 2285 | const new_ip = switch (builtin.cpu.arch) { | |
| 2286 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 2287 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 2288 | .RBP_FRAME => blk: { | |
| 2289 | const regs: [5]u3 = .{ | |
| 2290 | encoding.value.x86_64.frame.reg0, | |
| 2291 | encoding.value.x86_64.frame.reg1, | |
| 2292 | encoding.value.x86_64.frame.reg2, | |
| 2293 | encoding.value.x86_64.frame.reg3, | |
| 2294 | encoding.value.x86_64.frame.reg4, | |
| 2295 | }; | |
| 2296 | ||
| 2297 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | |
| 2298 | var max_reg: usize = 0; | |
| 2299 | inline for (regs, 0..) |reg, i| { | |
| 2300 | if (reg > 0) max_reg = i; | |
| 2301 | } | |
| 2302 | ||
| 2303 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | |
| 2304 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 2305 | ||
| 2306 | // Verify the stack range we're about to read register values from | |
| 2307 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo; | |
| 2308 | ||
| 2309 | const ip_ptr = fp + @sizeOf(usize); | |
| 2310 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2311 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 2312 | ||
| 2313 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 2314 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2315 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2316 | ||
| 2317 | for (regs, 0..) |reg, i| { | |
| 2318 | if (reg == 0) continue; | |
| 2319 | const addr = fp - frame_offset + i * @sizeOf(usize); | |
| 2320 | const reg_number = try dwarfRegNumber(reg); | |
| 2321 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 2322 | } | |
| 2323 | ||
| 2324 | break :blk new_ip; | |
| 2325 | }, | |
| 2326 | .STACK_IMMD, | |
| 2327 | .STACK_IND, | |
| 2328 | => blk: { | |
| 2329 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | |
| 2330 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | |
| 2331 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | |
| 2332 | else stack_size: { | |
| 2333 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 2334 | const sub_offset_addr = | |
| 2335 | module_base_address + | |
| 2336 | entry.function_offset + | |
| 2337 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | |
| 2338 | if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo; | |
| 2339 | ||
| 2340 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 2341 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 2342 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | |
| 2343 | }; | |
| 2344 | ||
| 2345 | // Decode the Lehmer-coded sequence of registers. | |
| 2346 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 2347 | ||
| 2348 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 2349 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 2350 | // the time the digit was added. | |
| 2351 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | |
| 2352 | const ip_ptr = if (reg_count > 0) reg_blk: { | |
| 2353 | var digits: [6]u3 = undefined; | |
| 2354 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | |
| 2355 | var base: usize = 2; | |
| 2356 | for (0..reg_count) |i| { | |
| 2357 | const div = accumulator / base; | |
| 2358 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 2359 | accumulator = div; | |
| 2360 | base += 1; | |
| 2361 | } | |
| 2362 | ||
| 2363 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | |
| 2364 | var registers: [reg_numbers.len]u3 = undefined; | |
| 2365 | var used_indices = [_]bool{false} ** reg_numbers.len; | |
| 2366 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 2367 | var unused_count: u8 = 0; | |
| 2368 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 2369 | if (!used) { | |
| 2370 | if (target_unused_index == unused_count) break index; | |
| 2371 | unused_count += 1; | |
| 2372 | } | |
| 2373 | } else unreachable; | |
| 2374 | ||
| 2375 | registers[i] = reg_numbers[unused_index]; | |
| 2376 | used_indices[unused_index] = true; | |
| 2377 | } | |
| 2378 | ||
| 2379 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 2380 | if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo; | |
| 2381 | for (0..reg_count) |i| { | |
| 2382 | const reg_number = try dwarfRegNumber(registers[i]); | |
| 2383 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2384 | reg_addr += @sizeOf(usize); | |
| 2385 | } | |
| 2386 | ||
| 2387 | break :reg_blk reg_addr; | |
| 2388 | } else sp + stack_size - @sizeOf(usize); | |
| 2389 | ||
| 2390 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2391 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 2392 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | |
| 2393 | ||
| 2394 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2395 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2396 | ||
| 2397 | break :blk new_ip; | |
| 2398 | }, | |
| 2399 | .DWARF => { | |
| 2400 | return unwindFrameDwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | |
| 2401 | }, | |
| 2402 | }, | |
| 2403 | .aarch64 => switch (encoding.mode.arm64) { | |
| 2404 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 2405 | .FRAMELESS => blk: { | |
| 2406 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | |
| 2407 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 2408 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | |
| 2409 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | |
| 2410 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | |
| 2411 | break :blk new_ip; | |
| 2412 | }, | |
| 2413 | .DWARF => { | |
| 2414 | return unwindFrameDwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | |
| 2415 | }, | |
| 2416 | .FRAME => blk: { | |
| 2417 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | |
| 2418 | const new_sp = fp + 16; | |
| 2419 | const ip_ptr = fp + @sizeOf(usize); | |
| 2420 | ||
| 2421 | const num_restored_pairs: usize = | |
| 2422 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | |
| 2423 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | |
| 2424 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | |
| 2425 | ||
| 2426 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo; | |
| 2427 | ||
| 2428 | var reg_addr = fp - @sizeOf(usize); | |
| 2429 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | |
| 2430 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | |
| 2431 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2432 | reg_addr += @sizeOf(usize); | |
| 2433 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 2434 | reg_addr += @sizeOf(usize); | |
| 2435 | } | |
| 2436 | } | |
| 2437 | ||
| 2438 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | |
| 2439 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | |
| 2440 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 2441 | @memcpy( | |
| 2442 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | |
| 2443 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 2444 | ); | |
| 2445 | reg_addr += @sizeOf(usize); | |
| 2446 | @memcpy( | |
| 2447 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | |
| 2448 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 2449 | ); | |
| 2450 | reg_addr += @sizeOf(usize); | |
| 2451 | } | |
| 2452 | } | |
| 2453 | ||
| 2454 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 2455 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 2456 | ||
| 2457 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 2458 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | |
| 2459 | ||
| 2460 | break :blk new_ip; | |
| 2461 | }, | |
| 2462 | }, | |
| 2463 | else => return error.UnimplementedArch, | |
| 2464 | }; | |
| 2465 | ||
| 2466 | context.pc = dwarf.abi.stripInstructionPtrAuthCode(new_ip); | |
| 2467 | if (context.pc > 0) context.pc -= 1; | |
| 2468 | return new_ip; | |
| 2469 | } | |
| 2470 | ||
| 2471 | fn unwindFrameDwarf(context: *dwarf.UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize { | |
| 2472 | var di = dwarf.DwarfInfo{ | |
| 2473 | .endian = builtin.cpu.arch.endian(), | |
| 2474 | .is_macho = true, | |
| 2475 | }; | |
| 2476 | defer di.deinit(context.allocator); | |
| 2477 | ||
| 2478 | di.sections[@intFromEnum(dwarf.DwarfSection.eh_frame)] = .{ | |
| 2479 | .data = eh_frame, | |
| 2480 | .owned = false, | |
| 2481 | }; | |
| 2482 | ||
| 2483 | return di.unwindFrame(context, fde_offset); | |
| 2484 | } |