├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── main.rs ├── notes ├── mod.rs ├── nt_file.rs └── nt_prpsinfo.rs └── vm.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.15" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "is_terminal_polyfill", 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle" 43 | version = "1.0.8" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 46 | 47 | [[package]] 48 | name = "anstyle-parse" 49 | version = "0.2.5" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 52 | dependencies = [ 53 | "utf8parse", 54 | ] 55 | 56 | [[package]] 57 | name = "anstyle-query" 58 | version = "1.1.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 61 | dependencies = [ 62 | "windows-sys", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-wincon" 67 | version = "3.0.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 70 | dependencies = [ 71 | "anstyle", 72 | "windows-sys", 73 | ] 74 | 75 | [[package]] 76 | name = "anyhow" 77 | version = "1.0.86" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 80 | 81 | [[package]] 82 | name = "cfg-if" 83 | version = "1.0.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 86 | 87 | [[package]] 88 | name = "colorchoice" 89 | version = "1.0.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 92 | 93 | [[package]] 94 | name = "coredump-copy" 95 | version = "0.1.6" 96 | dependencies = [ 97 | "anyhow", 98 | "elf", 99 | "env_logger", 100 | "log", 101 | "object", 102 | ] 103 | 104 | [[package]] 105 | name = "crc32fast" 106 | version = "1.4.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 109 | dependencies = [ 110 | "cfg-if", 111 | ] 112 | 113 | [[package]] 114 | name = "elf" 115 | version = "0.7.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" 118 | 119 | [[package]] 120 | name = "env_filter" 121 | version = "0.1.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 124 | dependencies = [ 125 | "log", 126 | "regex", 127 | ] 128 | 129 | [[package]] 130 | name = "env_logger" 131 | version = "0.11.5" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" 134 | dependencies = [ 135 | "anstream", 136 | "anstyle", 137 | "env_filter", 138 | "humantime", 139 | "log", 140 | ] 141 | 142 | [[package]] 143 | name = "equivalent" 144 | version = "1.0.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 147 | 148 | [[package]] 149 | name = "hashbrown" 150 | version = "0.14.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 153 | dependencies = [ 154 | "ahash", 155 | ] 156 | 157 | [[package]] 158 | name = "humantime" 159 | version = "2.1.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 162 | 163 | [[package]] 164 | name = "indexmap" 165 | version = "2.2.6" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 168 | dependencies = [ 169 | "equivalent", 170 | "hashbrown", 171 | ] 172 | 173 | [[package]] 174 | name = "is_terminal_polyfill" 175 | version = "1.70.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 178 | 179 | [[package]] 180 | name = "log" 181 | version = "0.4.22" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 184 | 185 | [[package]] 186 | name = "memchr" 187 | version = "2.7.4" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 190 | 191 | [[package]] 192 | name = "object" 193 | version = "0.36.2" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" 196 | dependencies = [ 197 | "crc32fast", 198 | "hashbrown", 199 | "indexmap", 200 | "memchr", 201 | ] 202 | 203 | [[package]] 204 | name = "once_cell" 205 | version = "1.19.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 208 | 209 | [[package]] 210 | name = "proc-macro2" 211 | version = "1.0.86" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 214 | dependencies = [ 215 | "unicode-ident", 216 | ] 217 | 218 | [[package]] 219 | name = "quote" 220 | version = "1.0.36" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 223 | dependencies = [ 224 | "proc-macro2", 225 | ] 226 | 227 | [[package]] 228 | name = "regex" 229 | version = "1.10.5" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 232 | dependencies = [ 233 | "aho-corasick", 234 | "memchr", 235 | "regex-automata", 236 | "regex-syntax", 237 | ] 238 | 239 | [[package]] 240 | name = "regex-automata" 241 | version = "0.4.7" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 244 | dependencies = [ 245 | "aho-corasick", 246 | "memchr", 247 | "regex-syntax", 248 | ] 249 | 250 | [[package]] 251 | name = "regex-syntax" 252 | version = "0.8.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 255 | 256 | [[package]] 257 | name = "syn" 258 | version = "2.0.70" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" 261 | dependencies = [ 262 | "proc-macro2", 263 | "quote", 264 | "unicode-ident", 265 | ] 266 | 267 | [[package]] 268 | name = "unicode-ident" 269 | version = "1.0.12" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 272 | 273 | [[package]] 274 | name = "utf8parse" 275 | version = "0.2.2" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 278 | 279 | [[package]] 280 | name = "version_check" 281 | version = "0.9.4" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 284 | 285 | [[package]] 286 | name = "windows-sys" 287 | version = "0.52.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 290 | dependencies = [ 291 | "windows-targets", 292 | ] 293 | 294 | [[package]] 295 | name = "windows-targets" 296 | version = "0.52.6" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 299 | dependencies = [ 300 | "windows_aarch64_gnullvm", 301 | "windows_aarch64_msvc", 302 | "windows_i686_gnu", 303 | "windows_i686_gnullvm", 304 | "windows_i686_msvc", 305 | "windows_x86_64_gnu", 306 | "windows_x86_64_gnullvm", 307 | "windows_x86_64_msvc", 308 | ] 309 | 310 | [[package]] 311 | name = "windows_aarch64_gnullvm" 312 | version = "0.52.6" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 315 | 316 | [[package]] 317 | name = "windows_aarch64_msvc" 318 | version = "0.52.6" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 321 | 322 | [[package]] 323 | name = "windows_i686_gnu" 324 | version = "0.52.6" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 327 | 328 | [[package]] 329 | name = "windows_i686_gnullvm" 330 | version = "0.52.6" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 333 | 334 | [[package]] 335 | name = "windows_i686_msvc" 336 | version = "0.52.6" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 339 | 340 | [[package]] 341 | name = "windows_x86_64_gnu" 342 | version = "0.52.6" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 345 | 346 | [[package]] 347 | name = "windows_x86_64_gnullvm" 348 | version = "0.52.6" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 351 | 352 | [[package]] 353 | name = "windows_x86_64_msvc" 354 | version = "0.52.6" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 357 | 358 | [[package]] 359 | name = "zerocopy" 360 | version = "0.7.34" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 363 | dependencies = [ 364 | "zerocopy-derive", 365 | ] 366 | 367 | [[package]] 368 | name = "zerocopy-derive" 369 | version = "0.7.34" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 372 | dependencies = [ 373 | "proc-macro2", 374 | "quote", 375 | "syn", 376 | ] 377 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coredump-copy" 3 | version = "0.1.6" 4 | edition = "2021" 5 | description = "Copy coredumps for debugging on a different machine" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | object = { version = "0.36.2", default-features = false, features = ["elf", "read_core", "write_std"] } 10 | anyhow = "1.0.86" 11 | env_logger = "0.11.5" 12 | log = "0.4.22" 13 | elf = "0.7.4" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coredump-copy 2 | ============= 3 | 4 | # Usage 5 | 6 | ``` 7 | coredump-copy 8 | ``` 9 | 10 | 11 | - ``: The core dump file to copy. This will copy the core dump file along with all the files it references. The resulting core dump will be modified to reference the new paths. The core dump file will be named `/core`. 12 | - ``: Where files should be copied to. 13 | 14 | # Why 15 | 16 | Sometimes you might want to copy a core dump file to another machine, and debug it there. For example, when your program crashed on a remote machine, or in CI. 17 | 18 | It is not enough to just copy the core dump file and the main binary, you also need to copy all the shared libraries and also maintain the directory structure, and set a prefix in gdb so it can find them. This program does all of that for you, automatically. 19 | 20 | # Caveat 21 | 22 | This program is not fool proof. Since it mangles with the core dump files, it is possible that it can do something wrong and break them. Report a bug if this doesn't work. 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | ffi::{OsStr, OsString}, 4 | mem::MaybeUninit, 5 | os::unix::{ffi::OsStrExt as _, fs::PermissionsExt}, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | mod notes; 10 | mod vm; 11 | 12 | use anyhow::Result; 13 | use notes::nt_file::NtFileAny; 14 | use object::{ 15 | read::elf::{Dyn as _, ElfFile, FileHeader, ProgramHeader}, 16 | Object, 17 | }; 18 | use vm::Pod as _; 19 | 20 | trait BitWidth: Copy + Clone { 21 | /// SAFETY: the slice must be at least `Self::BYTES` long. 22 | unsafe fn from_bytes(bytes: &[u8], endian: impl object::Endian) -> Self; 23 | /// SAFETY: the slice must be at least `Self::BYTES` long. 24 | unsafe fn to_bytes(&self, endian: impl object::Endian, bytes: &mut [MaybeUninit]); 25 | fn as_usize(&self) -> usize; 26 | fn from_u64(val: u64) -> Self; 27 | /// Convert the value from `endian` to the native endian. 28 | fn to_native_endian(&self, endian: impl object::Endian) -> Self; 29 | fn from_native_endian(&self, endian: impl object::Endian) -> Self; 30 | } 31 | impl BitWidth for u32 { 32 | unsafe fn from_bytes(bytes: &[u8], endian: impl object::Endian) -> Self { 33 | endian.read_u32_bytes(*(bytes.as_ptr() as *const [u8; 4])) 34 | } 35 | unsafe fn to_bytes(&self, endian: impl object::Endian, bytes: &mut [MaybeUninit]) { 36 | let arr = endian.write_u32_bytes(*self); 37 | bytes[0].as_mut_ptr().copy_from(arr.as_ptr(), 4); 38 | } 39 | fn as_usize(&self) -> usize { 40 | *self as usize 41 | } 42 | fn from_u64(val: u64) -> Self { 43 | Self::try_from(val).unwrap() 44 | } 45 | fn to_native_endian(&self, endian: impl object::Endian) -> Self { 46 | endian.read_u32(*self) 47 | } 48 | fn from_native_endian(&self, endian: impl object::Endian) -> Self { 49 | endian.write_u32(*self) 50 | } 51 | } 52 | impl BitWidth for u64 { 53 | unsafe fn from_bytes(bytes: &[u8], endian: impl object::Endian) -> Self { 54 | endian.read_u64_bytes(*(bytes.as_ptr() as *const [u8; 8])) 55 | } 56 | unsafe fn to_bytes(&self, endian: impl object::Endian, bytes: &mut [MaybeUninit]) { 57 | let arr = endian.write_u64_bytes(*self); 58 | bytes[0].as_mut_ptr().copy_from(arr.as_ptr(), 8); 59 | } 60 | fn as_usize(&self) -> usize { 61 | *self as usize 62 | } 63 | fn from_u64(val: u64) -> Self { 64 | val 65 | } 66 | fn to_native_endian(&self, endian: impl object::Endian) -> Self { 67 | endian.read_u64(*self) 68 | } 69 | fn from_native_endian(&self, endian: impl object::Endian) -> Self { 70 | endian.write_u64(*self) 71 | } 72 | } 73 | 74 | trait EndiannessExt { 75 | /// Split the slice at the given index. 76 | /// 77 | /// Returns a tuple of the slice up to the index as a fixed-size array, if the slice is long 78 | /// enough, and the rest of the slice. 79 | fn read_bits(&self, data: &mut &[u8]) -> Option; 80 | fn write_bits(&self, value: T, data: &mut Vec); 81 | fn to_endianness(&self) -> object::Endianness; 82 | } 83 | 84 | impl EndiannessExt for T { 85 | fn read_bits(&self, data: &mut &[u8]) -> Option { 86 | if data.len() < std::mem::size_of::() { 87 | None 88 | } else { 89 | // SAFETY: we just checked that the slice is long enough 90 | unsafe { 91 | let (a, b) = data.split_at_unchecked(std::mem::size_of::()); 92 | *data = b; 93 | Some(Output::from_bytes(a, *self)) 94 | } 95 | } 96 | } 97 | fn write_bits<'a, Output: BitWidth>(&self, value: Output, data: &mut Vec) { 98 | data.reserve(std::mem::size_of::()); 99 | // SAFETY: we just made sure that the slice is long enough 100 | unsafe { 101 | let rest = data.spare_capacity_mut(); 102 | value.to_bytes(*self, rest); 103 | data.set_len(data.len() + std::mem::size_of::()); 104 | } 105 | } 106 | fn to_endianness(&self) -> object::Endianness { 107 | if self.is_big_endian() { 108 | object::Endianness::Big 109 | } else { 110 | object::Endianness::Little 111 | } 112 | } 113 | } 114 | 115 | struct NotesIter<'a, F: FileHeader>(object::read::elf::NoteIterator<'a, F>); 116 | 117 | impl<'a, F: FileHeader> Iterator for NotesIter<'a, F> { 118 | type Item = std::result::Result, object::Error>; 119 | fn next(&mut self) -> Option { 120 | self.0.next().transpose() 121 | } 122 | } 123 | 124 | fn find_dt_debug_vaddr( 125 | main_exec_name: &[u8], 126 | elf: &ElfFile, 127 | mappings: &NtFileAny<'_>, 128 | ) -> Option { 129 | let ph = elf.elf_program_headers().iter().find_map(|ph| { 130 | ph.dynamic(elf.endian(), elf.data()) 131 | .ok() 132 | .flatten() 133 | .map(|_| ph) 134 | })?; 135 | let vaddr = ph.p_vaddr(elf.endian()).into(); 136 | let data = ph.data(elf.endian(), elf.data()).ok()?; 137 | let ph_offset = ph.p_offset(elf.endian()).into(); 138 | log::info!( 139 | "Found dynamic section, vaddr {:x}, size {:x}, offset: {ph_offset:x}", 140 | vaddr, 141 | ph.p_memsz(elf.endian()).into(), 142 | ); 143 | let mut offset = 0; 144 | while offset < data.len() { 145 | let (dyn_, _): (&F::Dyn, _) = object::pod::from_bytes(&data[offset..]).ok()?; 146 | log::debug!( 147 | "{:?}: {}, vaddr: {:x}", 148 | elf::to_str::d_tag_to_str(dyn_.d_tag(elf.endian()).into() as _), 149 | dyn_.d_val(elf.endian()).into(), 150 | offset as u64 + vaddr, 151 | ); 152 | if dyn_.d_tag(elf.endian()).into() == object::elf::DT_DEBUG as u64 { 153 | return mappings.file_offset_to_vaddr( 154 | main_exec_name, 155 | ph_offset + offset as u64 + /*skip d_tag*/elf.architecture().address_size().unwrap() as u64, 156 | ); 157 | } 158 | offset += std::mem::size_of::(); 159 | } 160 | None 161 | } 162 | 163 | #[repr(C)] 164 | #[derive(Clone, Copy)] 165 | struct RDebug { 166 | r_version: u32, 167 | r_map: Ptr, 168 | r_brk: Ptr, 169 | r_state: u32, 170 | r_ldbase: Ptr, 171 | } 172 | 173 | impl + Copy> std::fmt::Debug for RDebug { 174 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 175 | f.debug_struct("RDebug") 176 | .field("r_version", &self.r_version) 177 | .field("r_map", &self.r_map.into()) 178 | .field("r_brk", &self.r_brk.into()) 179 | .field("r_state", &self.r_state) 180 | .field("r_ldbase", &self.r_ldbase.into()) 181 | .finish() 182 | } 183 | } 184 | 185 | #[repr(C)] 186 | #[derive(Debug)] 187 | struct RawLinkMap { 188 | l_addr: Ptr, 189 | l_name: Ptr, 190 | l_ld: Ptr, 191 | l_next: Ptr, 192 | l_prev: Ptr, 193 | } 194 | 195 | #[derive(Debug)] 196 | struct LinkMap { 197 | addr: u64, 198 | name: OsString, 199 | ld: u64, 200 | #[allow(dead_code)] 201 | next: u64, 202 | prev: u64, 203 | } 204 | 205 | impl LinkMap { 206 | fn write( 207 | &self, 208 | endian: impl object::Endian, 209 | data: &mut Vec, 210 | base_addr: u64, 211 | is_last: bool, 212 | ) -> usize { 213 | let initial_len = data.len(); 214 | let expected_len = 215 | initial_len + std::mem::size_of::>() + self.name.len() + 1; 216 | let expected_len = 217 | (expected_len + std::mem::align_of::() - 1) & !(std::mem::align_of::() - 1); 218 | endian.write_bits(Ptr::from_u64(self.addr), data); 219 | endian.write_bits( 220 | Ptr::from_u64(base_addr + std::mem::size_of::>() as u64), 221 | data, 222 | ); 223 | endian.write_bits(Ptr::from_u64(self.ld), data); 224 | let next = if is_last { 225 | 0 226 | } else { 227 | (expected_len - initial_len) as u64 + base_addr 228 | }; 229 | log::info!("next: {:x}, prev: {:x}", next, self.prev); 230 | endian.write_bits(Ptr::from_u64(next), data); 231 | endian.write_bits(Ptr::from_u64(self.prev), data); 232 | data.extend(self.name.as_bytes()); 233 | data.push(0); 234 | let align = std::mem::align_of::(); 235 | let padding = (align - data.len() % align) % align; 236 | data.extend(std::iter::repeat(0).take(padding)); 237 | 238 | assert_eq!(data.len(), expected_len); 239 | data.len() - initial_len 240 | } 241 | } 242 | 243 | explicitly_size!(RDebug); 244 | explicitly_size!(RDebug); 245 | 246 | unsafe impl vm::Pod for RDebug 247 | where 248 | Self: vm::ExplicitlySized, 249 | { 250 | fn fix_endian(&mut self, endian: impl object::Endian) { 251 | self.r_version = self.r_version.to_native_endian(endian); 252 | self.r_map = self.r_map.to_native_endian(endian); 253 | self.r_brk = self.r_brk.to_native_endian(endian); 254 | self.r_state = self.r_state.to_native_endian(endian); 255 | self.r_ldbase = self.r_ldbase.to_native_endian(endian); 256 | } 257 | fn as_bytes(&self, endian: impl object::Endian) -> ::CopyArr { 258 | let foreign_endian = Self { 259 | r_version: self.r_version.from_native_endian(endian), 260 | r_map: self.r_map.from_native_endian(endian), 261 | r_brk: self.r_brk.from_native_endian(endian), 262 | r_state: self.r_state.from_native_endian(endian), 263 | r_ldbase: self.r_ldbase.from_native_endian(endian), 264 | }; 265 | unsafe { *(&foreign_endian as *const _ as *const _) } 266 | } 267 | } 268 | 269 | explicitly_size!(RawLinkMap); 270 | explicitly_size!(RawLinkMap); 271 | 272 | unsafe impl vm::Pod for RawLinkMap 273 | where 274 | Self: vm::ExplicitlySized, 275 | { 276 | fn fix_endian(&mut self, endian: impl object::Endian) { 277 | self.l_addr = self.l_addr.to_native_endian(endian); 278 | self.l_name = self.l_name.to_native_endian(endian); 279 | self.l_ld = self.l_ld.to_native_endian(endian); 280 | self.l_next = self.l_next.to_native_endian(endian); 281 | self.l_prev = self.l_prev.to_native_endian(endian); 282 | } 283 | fn as_bytes(&self, _endian: impl object::Endian) -> ::CopyArr { 284 | unimplemented!("Don't write RawLinkMap directly, use LinkMap::write") 285 | } 286 | } 287 | 288 | fn handle_elf( 289 | elf: &ElfFile, 290 | base_dir: impl AsRef, 291 | ) -> Result<()> 292 | where 293 | ::Word: Copy + TryFrom + TryFrom + BitWidth, 294 | RDebug: vm::Pod, 295 | RawLinkMap: vm::Pod, 296 | <::Word as TryFrom>::Error: std::error::Error + Send + Sync, 297 | <::Word as TryFrom>::Error: std::error::Error + Send + Sync, 298 | { 299 | let mut nt_file: Option> = None; 300 | let mut fname = None; 301 | for ph in elf.elf_program_headers() { 302 | let notes = ph.notes(elf.endian(), elf.data())?; 303 | if let Some(notes) = notes { 304 | for note in NotesIter(notes) { 305 | let note = note?; 306 | if note.n_type(elf.endian()) == object::elf::NT_PRPSINFO { 307 | let info = notes::nt_prpsinfo::PsInfo::parse(¬e, elf.endian()); 308 | log::info!("fname is: {}", std::str::from_utf8(info.fname).unwrap()); 309 | fname = Some(info.fname); 310 | continue; 311 | } 312 | if note.n_type(elf.endian()) != object::elf::NT_FILE { 313 | continue; 314 | } 315 | if nt_file.is_some() { 316 | return Err(anyhow::anyhow!("Mutiple NT_FILE notes found in core")); 317 | } 318 | nt_file = Some(notes::nt_file::NtFileAny::parse(¬e, elf.endian())?); 319 | } 320 | } 321 | } 322 | 323 | let Some(nt_file) = nt_file else { 324 | return Err(anyhow::anyhow!("No NT_FILE note found in core")); 325 | }; 326 | let Some(fname) = fname else { 327 | return Err(anyhow::anyhow!("No NT_PRPSINFO note found in core")); 328 | }; 329 | let nul_pos = fname.iter().position(|&b| b == 0).unwrap_or(fname.len()); 330 | let fname = &fname[..nul_pos]; 331 | 332 | let base_dir = base_dir.as_ref(); 333 | let mut copied = HashSet::::new(); 334 | let Some(main_exec) = nt_file.files().find(|f| { 335 | let src_path = std::path::Path::new(OsStr::from_bytes(f.file())); 336 | let Some(filename) = src_path.file_name() else { 337 | return false; 338 | }; 339 | if filename.as_bytes().starts_with(fname) { 340 | return true; 341 | } 342 | false 343 | }) else { 344 | return Err(anyhow::anyhow!( 345 | "Couldn't find main executable in NT_FILE notes" 346 | )); 347 | }; 348 | log::info!( 349 | "Main executable is: {}", 350 | std::str::from_utf8(main_exec.file())? 351 | ); 352 | 353 | let main_exec_name = main_exec.file(); 354 | let main_exec = Path::new(OsStr::from_bytes(main_exec_name)); 355 | if main_exec.parent().unwrap() != base_dir { 356 | let path = base_dir.join(main_exec.file_name().unwrap()); 357 | log::info!("Copying {} to {}", main_exec.display(), path.display()); 358 | std::fs::copy(main_exec, &path).unwrap_or_else(|e| { 359 | log::error!( 360 | "Couldn't copy file from {} to {}: {e}", 361 | main_exec.display(), 362 | path.display() 363 | ); 364 | 0 365 | }); 366 | if path.exists() { 367 | std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).ok(); 368 | } 369 | copied.insert(main_exec.to_path_buf()); 370 | } 371 | let main_exec = std::fs::read(main_exec)?; 372 | let main_exec = object::File::parse(&*main_exec)?; 373 | let Some(dt_debug) = (match &main_exec { 374 | object::File::Elf32(elf) => find_dt_debug_vaddr(main_exec_name, elf, &nt_file), 375 | object::File::Elf64(elf) => find_dt_debug_vaddr(main_exec_name, elf, &nt_file), 376 | _ => None, 377 | }) else { 378 | return Err(anyhow::anyhow!("Couldn't find DT_DEBUG in main executable")); 379 | }; 380 | log::info!("vaddr of DT_DEBUG is: {:x}", dt_debug); 381 | 382 | let vm = vm::Vm::load_object(elf)?; 383 | let r_debug = vm.read_ptr(dt_debug, elf.endian())?; 384 | let mut r_debug = vm.read_pod::>(r_debug, elf.endian())?; 385 | log::info!("r_debug: {:x?}", r_debug); 386 | let mut link_map = r_debug.r_map; 387 | let mut name_buf = Vec::new(); 388 | let mut link_map_size = std::mem::size_of::>(); 389 | let mut link_maps = Vec::new(); 390 | while link_map.into() != 0 { 391 | let raw_lmap = vm.read_pod::>(link_map.into(), elf.endian())?; 392 | vm.read_until_nul(raw_lmap.l_name.into(), &mut name_buf)?; 393 | let lmap = LinkMap { 394 | addr: raw_lmap.l_addr.into(), 395 | name: OsStr::from_bytes(&name_buf).to_os_string(), 396 | ld: raw_lmap.l_ld.into(), 397 | next: raw_lmap.l_next.into(), 398 | prev: raw_lmap.l_prev.into(), 399 | }; 400 | log::info!( 401 | "link_map: {lmap:x?}, file name: {:?}", 402 | std::str::from_utf8(&name_buf) 403 | ); 404 | link_maps.push(lmap); 405 | link_map = raw_lmap.l_next; 406 | 407 | let src_path = std::path::Path::new(OsStr::from_bytes(&name_buf)); 408 | let Some(filename) = src_path.file_name() else { 409 | continue; 410 | }; 411 | let Some(parent) = src_path.parent() else { 412 | continue; 413 | }; 414 | let path = if parent != base_dir { 415 | base_dir.join(filename) 416 | } else { 417 | src_path.to_path_buf() 418 | }; 419 | if !copied.contains(src_path) { 420 | if src_path != path { 421 | log::info!("Copying {} to {}", src_path.display(), path.display()); 422 | std::fs::copy(src_path, &path).unwrap_or_else(|e| { 423 | log::error!( 424 | "Couldn't copy file from {} to {}: {e}", 425 | src_path.display(), 426 | path.display() 427 | ); 428 | 0 429 | }); 430 | if path.exists() { 431 | std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).ok(); 432 | } 433 | } 434 | copied.insert(src_path.to_path_buf()); 435 | } 436 | } 437 | 438 | for lmap in link_maps.iter_mut() { 439 | let src_path = std::path::Path::new(&lmap.name); 440 | if let Some(parent) = src_path.parent() { 441 | if let Some(filename) = src_path.file_name() { 442 | if parent != base_dir { 443 | lmap.name = base_dir.join(filename).as_os_str().to_os_string(); 444 | } 445 | } 446 | } 447 | link_map_size += std::mem::size_of::>() 448 | + vm.align_to_ptr(lmap.name.len() as u64 + 1) as usize; 449 | } 450 | // Synthesize a new link_map and replace the file names. 451 | // First, find a free address range. 452 | let free_addr = vm.find_free(link_map_size as u64).ok_or_else(|| { 453 | anyhow::anyhow!("Couldn't find a free address range for the new link_map") 454 | })?; 455 | let mut link_map_end = free_addr; 456 | log::info!( 457 | "Free address range: {:x}, size needed: {}, link map entries {}", 458 | link_map_end, 459 | link_map_size, 460 | link_maps.len() 461 | ); 462 | r_debug.r_map = ::Word::try_from( 463 | free_addr + std::mem::size_of::>() as u64, 464 | ) 465 | .unwrap(); 466 | link_map_end = free_addr + std::mem::size_of::>() as u64; 467 | 468 | let mut new_link_map = Vec::new(); 469 | new_link_map.extend_from_slice(r_debug.as_bytes(elf.endian()).as_ref()); 470 | let mut prev = 0; 471 | let link_maps_len = link_maps.len(); 472 | for (i, lmap) in link_maps.iter_mut().enumerate() { 473 | lmap.prev = prev; 474 | prev = link_map_end; 475 | let size = lmap.write::( 476 | elf.endian(), 477 | &mut new_link_map, 478 | link_map_end, 479 | i == link_maps_len - 1, 480 | ); 481 | link_map_end += size as u64; 482 | } 483 | log::info!( 484 | "Link map end: {:x}, actual size: {}", 485 | link_map_end, 486 | link_map_end - free_addr 487 | ); 488 | 489 | let output_file = std::fs::File::create(base_dir.join("core"))?; 490 | let mut output_file = object::write::StreamingBuffer::new(output_file); 491 | let mut writer = object::write::elf::Writer::new( 492 | elf.endian().to_endianness(), 493 | F::is_type_64_sized(), 494 | &mut output_file, 495 | ); 496 | let fh = elf.elf_header(); 497 | let endian = elf.endian(); 498 | writer.reserve_file_header(); 499 | log::info!("Header size: {}", writer.reserved_len()); 500 | writer.reserve_program_headers((fh.e_phnum(endian) + 1) as _); 501 | log::info!("Program headers size: {}", writer.reserved_len()); 502 | writer.write_file_header(&object::write::elf::FileHeader { 503 | abi_version: fh.e_ident().abi_version, 504 | os_abi: fh.e_ident().os_abi, 505 | e_type: fh.e_type(endian), 506 | e_machine: fh.e_machine(endian), 507 | e_entry: fh.e_entry(endian).into(), 508 | e_flags: fh.e_flags(endian), 509 | })?; 510 | let mut curr_offset = writer.reserved_len() as u64; 511 | for ph in elf.elf_program_headers() { 512 | let align = ph.p_align(endian).into(); 513 | if align > 0 { 514 | curr_offset = (curr_offset + align - 1) & !(align - 1); 515 | } 516 | writer.write_program_header(&object::write::elf::ProgramHeader { 517 | p_type: ph.p_type(endian), 518 | p_flags: ph.p_flags(endian), 519 | p_offset: curr_offset, 520 | p_vaddr: ph.p_vaddr(endian).into(), 521 | p_paddr: ph.p_paddr(endian).into(), 522 | p_filesz: ph.p_filesz(endian).into(), 523 | p_memsz: ph.p_memsz(endian).into(), 524 | p_align: ph.p_align(endian).into(), 525 | }); 526 | curr_offset += ph.p_filesz(endian).into(); 527 | } 528 | let curr_offset = (curr_offset + 4095) & !4095; 529 | writer.write_program_header(&object::write::elf::ProgramHeader { 530 | p_type: object::elf::PT_LOAD, 531 | p_flags: object::elf::PF_R | object::elf::PF_W, 532 | p_offset: curr_offset, 533 | p_vaddr: free_addr, 534 | p_paddr: 0, 535 | p_filesz: (link_map_end - free_addr) as u64, 536 | p_memsz: ((link_map_end - free_addr) as u64 + 4095) & !4095, 537 | p_align: 4096, 538 | }); 539 | for ph in elf.elf_program_headers() { 540 | writer.write_align(::Word::into(ph.p_align(endian)) as _); 541 | let vaddr = ph.p_vaddr(endian).into(); 542 | let memsz = ph.p_memsz(endian).into(); 543 | if vaddr <= dt_debug && vaddr + memsz > dt_debug { 544 | // Overwrite the DT_DEBUG pointer. 545 | let mut data_copy = ph.data(endian, elf.data()).unwrap().to_vec(); 546 | let offset = (dt_debug - vaddr) as usize; 547 | let ptr_size = std::mem::size_of::(); 548 | let ptr = ::Word::try_from(free_addr).unwrap(); 549 | unsafe { 550 | ptr.to_bytes( 551 | endian, 552 | &mut *(&mut data_copy[offset..offset + ptr_size] as *mut _ as *mut _), 553 | ); 554 | } 555 | writer.write(&data_copy); 556 | } else { 557 | writer.write(ph.data(endian, elf.data()).unwrap()) 558 | } 559 | } 560 | writer.write_align(4096); 561 | writer.write(&new_link_map); 562 | 563 | Ok(()) 564 | } 565 | 566 | fn main() -> anyhow::Result<()> { 567 | env_logger::init(); 568 | if std::env::args().len() != 3 { 569 | eprintln!( 570 | "Usage: {} ", 571 | std::env::args().next().unwrap() 572 | ); 573 | eprintln!(); 574 | eprintln!("Copy a core file and all its dependencies into ."); 575 | eprintln!("Rewrite paths in the core file to use the copied files."); 576 | return Ok(()); 577 | } 578 | let filename = std::env::args().nth(1).unwrap(); 579 | let filename = Path::new(&filename); 580 | let basedir = std::env::args().nth(2).unwrap(); 581 | let basedir = Path::new(&basedir); 582 | if filename 583 | .parent() 584 | .ok_or_else(|| anyhow::anyhow!("Invalid input file path"))? 585 | == basedir 586 | { 587 | return Err(anyhow::anyhow!("Source and destination is the same file")); 588 | } 589 | std::fs::create_dir_all(basedir)?; 590 | let data = std::fs::read(filename)?; 591 | let obj = object::File::parse(&*data)?; 592 | 593 | match obj { 594 | object::File::Elf32(elf) => handle_elf(&elf, basedir), 595 | object::File::Elf64(elf) => handle_elf(&elf, basedir), 596 | _ => Err(anyhow::anyhow!("unsupported object file format")), 597 | } 598 | } 599 | -------------------------------------------------------------------------------- /src/notes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod nt_file; 2 | pub mod nt_prpsinfo; 3 | -------------------------------------------------------------------------------- /src/notes/nt_file.rs: -------------------------------------------------------------------------------- 1 | //! Parser of NT_FILE notes 2 | 3 | use crate::{BitWidth, EndiannessExt as _}; 4 | use anyhow::Result; 5 | use object::read::elf::{FileHeader, Note}; 6 | 7 | #[derive(Debug)] 8 | pub struct MappedFile { 9 | start: T, 10 | end: T, 11 | offset: T, 12 | file: Vec, 13 | } 14 | 15 | #[derive(Clone, Copy, Debug)] 16 | pub enum MappedFileAny<'a> { 17 | MappedFile64(&'a MappedFile), 18 | MappedFile32(&'a MappedFile), 19 | } 20 | 21 | impl<'a> MappedFileAny<'a> { 22 | pub fn file(self) -> &'a [u8] { 23 | match self { 24 | MappedFileAny::MappedFile32(m) => &m.file, 25 | MappedFileAny::MappedFile64(m) => &m.file, 26 | } 27 | } 28 | } 29 | 30 | pub struct NtFile<'data, T> { 31 | count: T, 32 | pagesz: T, 33 | name: &'data [u8], 34 | files: Vec>, 35 | } 36 | 37 | pub enum NtFileAny<'data> { 38 | NtFile32(NtFile<'data, u32>), 39 | NtFile64(NtFile<'data, u64>), 40 | } 41 | 42 | impl<'data> From> for NtFileAny<'data> { 43 | fn from(nt_file: NtFile<'data, u32>) -> Self { 44 | NtFileAny::NtFile32(nt_file) 45 | } 46 | } 47 | impl<'data> From> for NtFileAny<'data> { 48 | fn from(nt_file: NtFile<'data, u64>) -> Self { 49 | NtFileAny::NtFile64(nt_file) 50 | } 51 | } 52 | 53 | fn parse_nt_file<'data, T: BitWidth + std::fmt::Display, Elf: FileHeader>( 54 | endian: ::Endian, 55 | note: &object::read::elf::Note<'data, Elf>, 56 | ) -> Result> { 57 | assert!(note.n_type(endian) == object::elf::NT_FILE); 58 | let mut data = note.desc(); 59 | let count = endian 60 | .read_bits::(&mut data) 61 | .ok_or_else(|| anyhow::anyhow!("not enough data"))?; 62 | let pagesz = endian 63 | .read_bits::(&mut data) 64 | .ok_or_else(|| anyhow::anyhow!("not enough data"))?; 65 | let mut files = Vec::new(); 66 | for _ in 0..count.as_usize() { 67 | let start = endian 68 | .read_bits::(&mut data) 69 | .ok_or_else(|| anyhow::anyhow!("not enough data"))?; 70 | let end = endian 71 | .read_bits::(&mut data) 72 | .ok_or_else(|| anyhow::anyhow!("not enough data"))?; 73 | let offset = endian 74 | .read_bits::(&mut data) 75 | .ok_or_else(|| anyhow::anyhow!("not enough data"))?; 76 | files.push(MappedFile { 77 | start, 78 | end, 79 | offset, 80 | file: vec![], 81 | }); 82 | } 83 | for file in &mut files { 84 | let file_name = std::ffi::CStr::from_bytes_until_nul(data)?; 85 | data = &data[file_name.to_bytes().len() + 1..]; 86 | file.file = file_name.to_bytes().to_vec(); 87 | } 88 | Ok(NtFile { 89 | count, 90 | pagesz, 91 | name: note.name(), 92 | files, 93 | }) 94 | } 95 | 96 | fn serialize_nt_note( 97 | endian: impl object::Endian, 98 | notes: &NtFile<'_, T>, 99 | data: &mut Vec, 100 | ) { 101 | endian.write_bits((notes.name.len() + 1) as u32, data); 102 | let descsz_offset = data.len(); 103 | endian.write_bits(0u32, data); // Set descsz to 0 for now 104 | endian.write_bits(object::elf::NT_FILE, data); 105 | data.extend_from_slice(notes.name); 106 | data.push(0); 107 | let padding = (4 - data.len() % 4) % 4; 108 | data.extend(std::iter::repeat(0).take(padding)); 109 | 110 | let len1 = data.len(); 111 | assert_eq!(len1 % 4, 0); 112 | 113 | endian.write_bits(notes.count, data); 114 | endian.write_bits(notes.pagesz, data); 115 | for file in ¬es.files { 116 | endian.write_bits(file.start, data); 117 | endian.write_bits(file.end, data); 118 | endian.write_bits(file.offset, data); 119 | } 120 | for file in ¬es.files { 121 | data.extend_from_slice(&file.file); 122 | data.push(0); 123 | } 124 | 125 | let descsz = data.len() - len1; 126 | let padding = (4 - (descsz % 4)) % 4; 127 | data.extend(std::iter::repeat(0).take(padding)); 128 | data[descsz_offset..descsz_offset + 4] 129 | .copy_from_slice(&endian.write_u32_bytes(descsz as u32)[..]); 130 | } 131 | impl<'a> NtFileAny<'a> { 132 | pub fn serialize(&self, endian: impl object::Endian, data: &mut Vec) { 133 | match self { 134 | NtFileAny::NtFile32(nt_file) => serialize_nt_note(endian, nt_file, data), 135 | NtFileAny::NtFile64(nt_file) => serialize_nt_note(endian, nt_file, data), 136 | } 137 | } 138 | pub fn pagesz(&self) -> u64 { 139 | match self { 140 | NtFileAny::NtFile32(nt_file) => nt_file.pagesz as u64, 141 | NtFileAny::NtFile64(nt_file) => nt_file.pagesz, 142 | } 143 | } 144 | pub fn parse<'b, F: FileHeader>(note: &'b Note<'a, F>, endian: F::Endian) -> Result { 145 | if F::is_type_64_sized() { 146 | Ok(NtFileAny::NtFile64(parse_nt_file(endian, note)?)) 147 | } else { 148 | Ok(NtFileAny::NtFile32(parse_nt_file(endian, note)?)) 149 | } 150 | } 151 | pub fn files(&self) -> impl Iterator> { 152 | enum NtFilesIter { 153 | A(A), 154 | B(B), 155 | } 156 | impl<'a, A: Iterator>, B: Iterator>> 157 | Iterator for NtFilesIter 158 | { 159 | type Item = MappedFileAny<'a>; 160 | fn next(&mut self) -> Option { 161 | match self { 162 | Self::A(a) => a.next(), 163 | Self::B(b) => b.next(), 164 | } 165 | } 166 | } 167 | match self { 168 | NtFileAny::NtFile32(f) => { 169 | NtFilesIter::A(f.files.iter().map(MappedFileAny::MappedFile32)) 170 | } 171 | NtFileAny::NtFile64(f) => { 172 | NtFilesIter::B(f.files.iter().map(MappedFileAny::MappedFile64)) 173 | } 174 | } 175 | } 176 | pub fn file_offset_to_vaddr(&self, fname: &[u8], offset: u64) -> Option { 177 | self.files().find_map(|file| { 178 | if file.file() == fname { 179 | let (start, end, page_offset) = match file { 180 | MappedFileAny::MappedFile32(f) => { 181 | (f.start as u64, f.end as u64, f.offset as u64) 182 | } 183 | MappedFileAny::MappedFile64(f) => (f.start, f.end, f.offset), 184 | }; 185 | let mapping_size = end - start; 186 | let file_offset = page_offset * self.pagesz(); 187 | if offset >= file_offset && offset < file_offset + mapping_size { 188 | return Some(start + offset - file_offset); 189 | } 190 | } 191 | None 192 | }) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/notes/nt_prpsinfo.rs: -------------------------------------------------------------------------------- 1 | // C version of prpsinfo: 2 | // 3 | // struct elf_prpsinfo 4 | // { 5 | // char pr_state; /* numeric process state */ 6 | // char pr_sname; /* char for pr_state */ 7 | // char pr_zomb; /* zombie */ 8 | // char pr_nice; /* nice val */ 9 | // unsigned long pr_flag; /* flags */ 10 | // __kernel_uid_t pr_uid; 11 | // __kernel_gid_t pr_gid; 12 | // pid_t pr_pid, pr_ppid, pr_pgrp, pr_sid; 13 | // /* Lots missing */ 14 | // /* 15 | // * The hard-coded 16 is derived from TASK_COMM_LEN, but it can't be 16 | // * changed as it is exposed to userspace. We'd better make it hard-coded 17 | // * here. 18 | // */ 19 | // char pr_fname[16]; /* filename of executable */ 20 | // char pr_psargs[ELF_PRARGSZ]; /* initial part of arg list */ 21 | // }; 22 | 23 | use object::read::elf::{FileHeader, Note}; 24 | 25 | const ELF_PRARGSZ: usize = 80; 26 | 27 | pub struct PsInfo<'a> { 28 | pub fname: &'a [u8], 29 | #[allow(dead_code, reason = "for completeness")] 30 | pub psargs: &'a [u8], 31 | } 32 | 33 | impl<'a> PsInfo<'a> { 34 | pub fn parse(note: &Note<'a, F>, endian: F::Endian) -> Self { 35 | assert!(note.n_type(endian) == object::elf::NT_PRPSINFO); 36 | let skip = 37 | 4 + if F::is_type_64_sized() { 38 | 4 /*padding*/ + 8 39 | } else { 40 | 4 41 | } + 4 42 | + 4 43 | + 4 * 4; 44 | let data = note.desc(); 45 | Self { 46 | fname: &data[skip..skip + 16], 47 | psargs: &data[skip + 16..skip + 16 + ELF_PRARGSZ], 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/vm.rs: -------------------------------------------------------------------------------- 1 | use crate::EndiannessExt as _; 2 | use anyhow::Result; 3 | use object::{ 4 | read::elf::{FileHeader, ProgramHeader as _}, 5 | Object, 6 | }; 7 | use std::{collections::BTreeMap, mem::MaybeUninit}; 8 | 9 | pub struct Segment<'a> { 10 | pub data: &'a [u8], 11 | pub memsz: u64, 12 | } 13 | 14 | impl<'a> Segment<'a> { 15 | fn read_into_uninit(&self, offset: u64, mut output: &mut [MaybeUninit]) -> Result { 16 | if output.is_empty() { 17 | return Ok(0); 18 | } 19 | if offset >= self.memsz { 20 | return Err(anyhow::anyhow!("Offset out of bounds")); 21 | } 22 | let mut offset2 = offset; 23 | let len = self.data.len() as u64; 24 | if offset2 < len { 25 | let to_read = (output.len() as u64).min(len - offset2); 26 | // SAFETY: we made sure `to_read` doesn't exceed the length of `self.data` or `output`. 27 | unsafe { 28 | output[0] 29 | .as_mut_ptr() 30 | .copy_from(self.data[offset2 as usize..].as_ptr(), to_read as usize); 31 | } 32 | output = &mut output[to_read as usize..]; 33 | offset2 += to_read; 34 | } 35 | if !output.is_empty() && offset2 < self.memsz { 36 | let to_zero = (output.len() as u64).min(self.memsz - offset2); 37 | output[..to_zero as usize].fill(MaybeUninit::new(0)); 38 | offset2 += to_zero; 39 | } 40 | Ok(offset2 - offset) 41 | } 42 | } 43 | 44 | /// Maps a elf file into memory, without _actually_ mapping it into memory. 45 | pub struct Vm<'a> { 46 | vma: BTreeMap>, 47 | addr_size: u8, 48 | } 49 | 50 | impl<'a> Vm<'a> { 51 | pub fn load_object(obj: &object::read::elf::ElfFile<'a, F>) -> Result { 52 | let mut vma = BTreeMap::new(); 53 | let addr_size = obj.architecture().address_size().unwrap() as u8; 54 | for segment in obj.elf_program_headers() { 55 | if segment.p_type(obj.endian()) != object::elf::PT_LOAD { 56 | log::debug!( 57 | "Segment at {:x} is not PT_LOAD", 58 | segment.p_vaddr(obj.endian()).into() 59 | ); 60 | continue; 61 | } 62 | log::debug!( 63 | "Inserting segment at {:x} with size {:x}", 64 | segment.p_vaddr(obj.endian()).into(), 65 | segment.p_memsz(obj.endian()).into() 66 | ); 67 | vma.insert( 68 | segment.p_vaddr(obj.endian()).into(), 69 | Segment { 70 | memsz: segment.p_memsz(obj.endian()).into(), 71 | data: segment 72 | .data(obj.endian(), obj.data()) 73 | .map_err(|_| anyhow::anyhow!("Failed to read segment"))?, 74 | }, 75 | ); 76 | } 77 | Ok(Vm { vma, addr_size }) 78 | } 79 | pub fn read_into_uninit<'b>( 80 | &self, 81 | vaddr: u64, 82 | output_: &'b mut [MaybeUninit], 83 | ) -> Result<&'b mut [u8]> { 84 | if output_.is_empty() { 85 | return Ok(&mut []); 86 | } 87 | let mut output = &mut *output_; 88 | let first = self 89 | .vma 90 | .range(..=vaddr) 91 | .last() 92 | .ok_or_else(|| anyhow::anyhow!("No segments in VM"))?; 93 | if first.0 + first.1.memsz <= vaddr { 94 | return Err(anyhow::anyhow!("Address not in any segment")); 95 | } 96 | let read = first.1.read_into_uninit(vaddr - first.0, output)?; 97 | output = &mut output[read as usize..]; 98 | let mut last_addr = first.0 + first.1.memsz; 99 | for range in self.vma.range(last_addr..) { 100 | if output.is_empty() { 101 | break; 102 | } 103 | if *range.0 != last_addr { 104 | return Err(anyhow::anyhow!("Read range has holes")); 105 | } 106 | let read = range.1.read_into_uninit(0, output)?; 107 | output = &mut output[read as usize..]; 108 | last_addr = range.0 + range.1.memsz; 109 | } 110 | if output.is_empty() { 111 | // SAFETY: `output_` is completely filled. 112 | return Ok(unsafe { &mut *(output_ as *mut [_] as *mut [u8]) }); 113 | } 114 | Err(anyhow::anyhow!("Not enough data in VM")) 115 | } 116 | pub fn read(&self, vaddr: u64, output: &mut [u8]) -> Result<()> { 117 | // SAFETY: `read_into_uninit` will not uninitialize an already initialized slice. 118 | let output = unsafe { &mut *(output as *mut _ as *mut [MaybeUninit]) }; 119 | self.read_into_uninit(vaddr, output)?; 120 | Ok(()) 121 | } 122 | /// Find the first occurrence of a byte, starting from `vaddr`. If `vaddr` is not a valid 123 | /// address, this function will return `None`. 124 | pub fn find_byte(&self, vaddr: u64, byte: u8) -> Option { 125 | let first = self.vma.range(..=vaddr).last()?; 126 | if first.0 + first.1.memsz <= vaddr { 127 | return None; 128 | } 129 | let slice = &first.1.data[(vaddr - first.0) as usize..]; 130 | if let Some(pos) = slice.iter().position(|&b| b == byte) { 131 | return Some(vaddr + pos as u64); 132 | } 133 | if first.1.memsz > first.1.data.len() as u64 { 134 | return Some(first.0 + first.1.data.len() as u64 + 1); 135 | } 136 | let mut last_addr = first.0 + first.1.memsz; 137 | for range in self.vma.range(vaddr + slice.len() as u64..) { 138 | if *range.0 != last_addr { 139 | return None; 140 | } 141 | if let Some(pos) = range.1.data.iter().position(|&b| b == byte) { 142 | return Some(range.0 + pos as u64); 143 | } 144 | if range.1.memsz > range.1.data.len() as u64 { 145 | return Some(range.0 + range.1.data.len() as u64 + 1); 146 | } 147 | last_addr = range.0 + range.1.memsz; 148 | } 149 | None 150 | } 151 | pub fn read_until_nul(&self, vaddr: u64, output: &mut Vec) -> Result<()> { 152 | let end = self 153 | .find_byte(vaddr, 0) 154 | .ok_or_else(|| anyhow::anyhow!("No NUL byte found"))?; 155 | log::info!("Read {:x} - {:x}", vaddr, end); 156 | output.clear(); 157 | if end == vaddr { 158 | return Ok(()); 159 | } 160 | output.reserve((end - vaddr) as usize); 161 | self.read_into_uninit( 162 | vaddr, 163 | &mut output.spare_capacity_mut()[..(end - vaddr) as usize], 164 | )?; 165 | unsafe { 166 | output.set_len((end - vaddr) as usize); 167 | } 168 | Ok(()) 169 | } 170 | pub fn read_ptr(&self, vaddr: u64, endian: impl object::Endian) -> Result { 171 | let mut buf = [0; 8]; 172 | self.read(vaddr, &mut buf[..self.addr_size as usize])?; 173 | let mut buf = &buf[..self.addr_size as usize]; 174 | Ok(match self.addr_size { 175 | 4 => endian.read_bits::(&mut buf).unwrap() as u64, 176 | 8 => endian.read_bits::(&mut buf).unwrap(), 177 | _ => unreachable!(), 178 | }) 179 | } 180 | pub fn align_to_ptr(&self, vaddr: u64) -> u64 { 181 | (vaddr + self.addr_size as u64 - 1) & !(self.addr_size as u64 - 1) 182 | } 183 | /// Find a free region of memory of at least `size` bytes. The start address will be page aligned. 184 | pub fn find_free(&self, size: u64) -> Option { 185 | for (curr, next) in self.vma.iter().zip(self.vma.iter().skip(1)) { 186 | const PAGE_SIZE: u64 = 4096; 187 | let curr_end = curr.0 + curr.1.memsz; 188 | let curr_end = (curr_end + PAGE_SIZE - 1) & !(PAGE_SIZE - 1); 189 | let next_start = next.0; 190 | if next_start - curr_end >= size { 191 | return Some(curr_end); 192 | } 193 | } 194 | None 195 | } 196 | pub fn read_pod(&self, vaddr: u64, endian: impl object::Endian) -> Result 197 | where 198 | Self: Sized, 199 | { 200 | let mut buf = std::mem::MaybeUninit::::uninit(); 201 | let mut this = unsafe { 202 | let buf_slice = 203 | std::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), std::mem::size_of::()); 204 | self.read_into_uninit(vaddr, buf_slice)?; 205 | buf.assume_init() 206 | }; 207 | this.fix_endian(endian); 208 | Ok(this) 209 | } 210 | #[allow(dead_code, reason = "placeholder")] 211 | fn as_bytes(&self) -> &[u8] { 212 | unsafe { 213 | std::slice::from_raw_parts( 214 | self as *const Self as *const u8, 215 | std::mem::size_of::(), 216 | ) 217 | } 218 | } 219 | } 220 | 221 | /// Pod 222 | /// 223 | /// # Safety 224 | /// 225 | /// Must be plain old data. 226 | pub unsafe trait Pod: ExplicitlySized + Sized { 227 | fn fix_endian(&mut self, endian: impl object::Endian); 228 | fn as_bytes(&self, endian: impl object::Endian) -> ::CopyArr; 229 | } 230 | 231 | /// A trait for storing size information about a type 232 | /// 233 | /// This is needed to workaround the limitation of not being able to use 234 | /// `std::mem::size_of` in const generics. (#![feature(genric_const_exprs)]). 235 | /// 236 | /// # Safety 237 | /// 238 | /// This must faithfully represent the size of the type. Don't implement this 239 | /// by hand, use the `explicitly_size!` macro. 240 | pub unsafe trait ExplicitlySized { 241 | #[allow(dead_code, reason = "For completeness")] 242 | const SIZE: usize; 243 | type Arr: AsRef<[T]> + AsMut<[T]>; 244 | type CopyArr: AsRef<[T]> + AsMut<[T]> + Clone + Copy 245 | where 246 | T: Copy + Clone; 247 | } 248 | 249 | #[macro_export] 250 | macro_rules! explicitly_size { 251 | ($t:ty) => { 252 | unsafe impl $crate::vm::ExplicitlySized for $t { 253 | const SIZE: usize = std::mem::size_of::<$t>(); 254 | type Arr = [T; std::mem::size_of::<$t>()]; 255 | type CopyArr = [T; std::mem::size_of::<$t>()] where T: Copy + Clone; 256 | } 257 | }; 258 | } 259 | --------------------------------------------------------------------------------