├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── elf ├── mod.rs ├── pass │ ├── init_array.rs │ ├── mod.rs │ ├── reloc.rs │ ├── section.rs │ └── symbol.rs └── test │ ├── libspdlog.so.1.12.0 │ └── mod.rs ├── main.rs ├── pass.rs └── utils ├── mod.rs └── stringify.rs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/rust:latest 2 | 3 | RUN apt-get update && \ 4 | export DEBIAN_FRONTEND=noninteractive && \ 5 | apt-get -y install binutils 6 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "build": { 7 | "dockerfile": "Dockerfile" 8 | }, 9 | 10 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 11 | // "mounts": [ 12 | // { 13 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 14 | // "target": "/usr/local/cargo", 15 | // "type": "volume" 16 | // } 17 | // ] 18 | 19 | // Features to add to the dev container. More info: https://containers.dev/features. 20 | // "features": {}, 21 | 22 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 23 | // "forwardPorts": [], 24 | 25 | // Use 'postCreateCommand' to run commands after the container is created. 26 | // "postCreateCommand": "rustc --version", 27 | 28 | // Configure tool-specific properties. 29 | "customizations": { 30 | "vscode": { 31 | "extensions": [ 32 | "ms-vscode.hexeditor", 33 | "swellaby.rust-pack", 34 | "vadimcn.vscode-lldb", 35 | "yzhang.markdown-all-in-one" 36 | ] 37 | } 38 | } 39 | 40 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 41 | // "remoteUser": "root" 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE related files. 2 | .idea/ 3 | .vscode/ 4 | 5 | # macOS Finder garbage. 6 | .DS_Store 7 | 8 | # Build directory. 9 | /target 10 | -------------------------------------------------------------------------------- /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 = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.8.6" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" 16 | dependencies = [ 17 | "cfg-if", 18 | "once_cell", 19 | "version_check", 20 | "zerocopy", 21 | ] 22 | 23 | [[package]] 24 | name = "ansi_term" 25 | version = "0.12.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 28 | dependencies = [ 29 | "winapi", 30 | ] 31 | 32 | [[package]] 33 | name = "anyhow" 34 | version = "1.0.75" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 43 | dependencies = [ 44 | "hermit-abi 0.1.19", 45 | "libc", 46 | "winapi", 47 | ] 48 | 49 | [[package]] 50 | name = "bitflags" 51 | version = "1.3.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 54 | 55 | [[package]] 56 | name = "bitflags" 57 | version = "2.4.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 60 | 61 | [[package]] 62 | name = "byteorder" 63 | version = "1.5.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "clap" 75 | version = "2.34.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 78 | dependencies = [ 79 | "ansi_term", 80 | "atty", 81 | "bitflags 1.3.2", 82 | "strsim", 83 | "textwrap", 84 | "unicode-width", 85 | "vec_map", 86 | ] 87 | 88 | [[package]] 89 | name = "colored" 90 | version = "2.0.4" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" 93 | dependencies = [ 94 | "is-terminal", 95 | "lazy_static", 96 | "windows-sys 0.48.0", 97 | ] 98 | 99 | [[package]] 100 | name = "crc32fast" 101 | version = "1.3.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 104 | dependencies = [ 105 | "cfg-if", 106 | ] 107 | 108 | [[package]] 109 | name = "equivalent" 110 | version = "1.0.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 113 | 114 | [[package]] 115 | name = "errno" 116 | version = "0.3.8" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 119 | dependencies = [ 120 | "libc", 121 | "windows-sys 0.52.0", 122 | ] 123 | 124 | [[package]] 125 | name = "flate2" 126 | version = "1.0.28" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 129 | dependencies = [ 130 | "crc32fast", 131 | "miniz_oxide", 132 | ] 133 | 134 | [[package]] 135 | name = "hashbrown" 136 | version = "0.14.3" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 139 | dependencies = [ 140 | "ahash", 141 | ] 142 | 143 | [[package]] 144 | name = "heck" 145 | version = "0.3.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 148 | dependencies = [ 149 | "unicode-segmentation", 150 | ] 151 | 152 | [[package]] 153 | name = "hermit-abi" 154 | version = "0.1.19" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 157 | dependencies = [ 158 | "libc", 159 | ] 160 | 161 | [[package]] 162 | name = "hermit-abi" 163 | version = "0.3.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 166 | 167 | [[package]] 168 | name = "indexmap" 169 | version = "2.1.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 172 | dependencies = [ 173 | "equivalent", 174 | "hashbrown", 175 | ] 176 | 177 | [[package]] 178 | name = "is-terminal" 179 | version = "0.4.9" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 182 | dependencies = [ 183 | "hermit-abi 0.3.3", 184 | "rustix", 185 | "windows-sys 0.48.0", 186 | ] 187 | 188 | [[package]] 189 | name = "lazy_static" 190 | version = "1.4.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 193 | 194 | [[package]] 195 | name = "libc" 196 | version = "0.2.150" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 199 | 200 | [[package]] 201 | name = "linux-raw-sys" 202 | version = "0.4.11" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" 205 | 206 | [[package]] 207 | name = "log" 208 | version = "0.4.20" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 211 | 212 | [[package]] 213 | name = "memchr" 214 | version = "2.6.4" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 217 | 218 | [[package]] 219 | name = "miniz_oxide" 220 | version = "0.7.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 223 | dependencies = [ 224 | "adler", 225 | ] 226 | 227 | [[package]] 228 | name = "object" 229 | version = "0.32.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 232 | dependencies = [ 233 | "crc32fast", 234 | "flate2", 235 | "hashbrown", 236 | "indexmap", 237 | "memchr", 238 | "ruzstd", 239 | ] 240 | 241 | [[package]] 242 | name = "once_cell" 243 | version = "1.18.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 246 | 247 | [[package]] 248 | name = "proc-macro-error" 249 | version = "1.0.4" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 252 | dependencies = [ 253 | "proc-macro-error-attr", 254 | "proc-macro2", 255 | "quote", 256 | "syn 1.0.109", 257 | "version_check", 258 | ] 259 | 260 | [[package]] 261 | name = "proc-macro-error-attr" 262 | version = "1.0.4" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 265 | dependencies = [ 266 | "proc-macro2", 267 | "quote", 268 | "version_check", 269 | ] 270 | 271 | [[package]] 272 | name = "proc-macro2" 273 | version = "1.0.70" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" 276 | dependencies = [ 277 | "unicode-ident", 278 | ] 279 | 280 | [[package]] 281 | name = "quote" 282 | version = "1.0.33" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 285 | dependencies = [ 286 | "proc-macro2", 287 | ] 288 | 289 | [[package]] 290 | name = "rustix" 291 | version = "0.38.25" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" 294 | dependencies = [ 295 | "bitflags 2.4.1", 296 | "errno", 297 | "libc", 298 | "linux-raw-sys", 299 | "windows-sys 0.48.0", 300 | ] 301 | 302 | [[package]] 303 | name = "ruzstd" 304 | version = "0.4.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "ac3ffab8f9715a0d455df4bbb9d21e91135aab3cd3ca187af0cd0c3c3f868fdc" 307 | dependencies = [ 308 | "byteorder", 309 | "thiserror-core", 310 | "twox-hash", 311 | ] 312 | 313 | [[package]] 314 | name = "simple_logger" 315 | version = "4.3.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" 318 | dependencies = [ 319 | "colored", 320 | "log", 321 | "windows-sys 0.48.0", 322 | ] 323 | 324 | [[package]] 325 | name = "soda" 326 | version = "0.1.0" 327 | dependencies = [ 328 | "anyhow", 329 | "log", 330 | "object", 331 | "simple_logger", 332 | "structopt", 333 | "thiserror", 334 | ] 335 | 336 | [[package]] 337 | name = "static_assertions" 338 | version = "1.1.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 341 | 342 | [[package]] 343 | name = "strsim" 344 | version = "0.8.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 347 | 348 | [[package]] 349 | name = "structopt" 350 | version = "0.3.26" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 353 | dependencies = [ 354 | "clap", 355 | "lazy_static", 356 | "structopt-derive", 357 | ] 358 | 359 | [[package]] 360 | name = "structopt-derive" 361 | version = "0.4.18" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 364 | dependencies = [ 365 | "heck", 366 | "proc-macro-error", 367 | "proc-macro2", 368 | "quote", 369 | "syn 1.0.109", 370 | ] 371 | 372 | [[package]] 373 | name = "syn" 374 | version = "1.0.109" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 377 | dependencies = [ 378 | "proc-macro2", 379 | "quote", 380 | "unicode-ident", 381 | ] 382 | 383 | [[package]] 384 | name = "syn" 385 | version = "2.0.39" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 388 | dependencies = [ 389 | "proc-macro2", 390 | "quote", 391 | "unicode-ident", 392 | ] 393 | 394 | [[package]] 395 | name = "textwrap" 396 | version = "0.11.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 399 | dependencies = [ 400 | "unicode-width", 401 | ] 402 | 403 | [[package]] 404 | name = "thiserror" 405 | version = "1.0.50" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 408 | dependencies = [ 409 | "thiserror-impl", 410 | ] 411 | 412 | [[package]] 413 | name = "thiserror-core" 414 | version = "1.0.50" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999" 417 | dependencies = [ 418 | "thiserror-core-impl", 419 | ] 420 | 421 | [[package]] 422 | name = "thiserror-core-impl" 423 | version = "1.0.50" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" 426 | dependencies = [ 427 | "proc-macro2", 428 | "quote", 429 | "syn 2.0.39", 430 | ] 431 | 432 | [[package]] 433 | name = "thiserror-impl" 434 | version = "1.0.50" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "syn 2.0.39", 441 | ] 442 | 443 | [[package]] 444 | name = "twox-hash" 445 | version = "1.6.3" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 448 | dependencies = [ 449 | "cfg-if", 450 | "static_assertions", 451 | ] 452 | 453 | [[package]] 454 | name = "unicode-ident" 455 | version = "1.0.12" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 458 | 459 | [[package]] 460 | name = "unicode-segmentation" 461 | version = "1.10.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 464 | 465 | [[package]] 466 | name = "unicode-width" 467 | version = "0.1.11" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 470 | 471 | [[package]] 472 | name = "vec_map" 473 | version = "0.8.2" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 476 | 477 | [[package]] 478 | name = "version_check" 479 | version = "0.9.4" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 482 | 483 | [[package]] 484 | name = "winapi" 485 | version = "0.3.9" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 488 | dependencies = [ 489 | "winapi-i686-pc-windows-gnu", 490 | "winapi-x86_64-pc-windows-gnu", 491 | ] 492 | 493 | [[package]] 494 | name = "winapi-i686-pc-windows-gnu" 495 | version = "0.4.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 498 | 499 | [[package]] 500 | name = "winapi-x86_64-pc-windows-gnu" 501 | version = "0.4.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 504 | 505 | [[package]] 506 | name = "windows-sys" 507 | version = "0.48.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 510 | dependencies = [ 511 | "windows-targets 0.48.5", 512 | ] 513 | 514 | [[package]] 515 | name = "windows-sys" 516 | version = "0.52.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 519 | dependencies = [ 520 | "windows-targets 0.52.0", 521 | ] 522 | 523 | [[package]] 524 | name = "windows-targets" 525 | version = "0.48.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 528 | dependencies = [ 529 | "windows_aarch64_gnullvm 0.48.5", 530 | "windows_aarch64_msvc 0.48.5", 531 | "windows_i686_gnu 0.48.5", 532 | "windows_i686_msvc 0.48.5", 533 | "windows_x86_64_gnu 0.48.5", 534 | "windows_x86_64_gnullvm 0.48.5", 535 | "windows_x86_64_msvc 0.48.5", 536 | ] 537 | 538 | [[package]] 539 | name = "windows-targets" 540 | version = "0.52.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 543 | dependencies = [ 544 | "windows_aarch64_gnullvm 0.52.0", 545 | "windows_aarch64_msvc 0.52.0", 546 | "windows_i686_gnu 0.52.0", 547 | "windows_i686_msvc 0.52.0", 548 | "windows_x86_64_gnu 0.52.0", 549 | "windows_x86_64_gnullvm 0.52.0", 550 | "windows_x86_64_msvc 0.52.0", 551 | ] 552 | 553 | [[package]] 554 | name = "windows_aarch64_gnullvm" 555 | version = "0.48.5" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 558 | 559 | [[package]] 560 | name = "windows_aarch64_gnullvm" 561 | version = "0.52.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 564 | 565 | [[package]] 566 | name = "windows_aarch64_msvc" 567 | version = "0.48.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 570 | 571 | [[package]] 572 | name = "windows_aarch64_msvc" 573 | version = "0.52.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 576 | 577 | [[package]] 578 | name = "windows_i686_gnu" 579 | version = "0.48.5" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 582 | 583 | [[package]] 584 | name = "windows_i686_gnu" 585 | version = "0.52.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 588 | 589 | [[package]] 590 | name = "windows_i686_msvc" 591 | version = "0.48.5" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 594 | 595 | [[package]] 596 | name = "windows_i686_msvc" 597 | version = "0.52.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 600 | 601 | [[package]] 602 | name = "windows_x86_64_gnu" 603 | version = "0.48.5" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 606 | 607 | [[package]] 608 | name = "windows_x86_64_gnu" 609 | version = "0.52.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 612 | 613 | [[package]] 614 | name = "windows_x86_64_gnullvm" 615 | version = "0.48.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 618 | 619 | [[package]] 620 | name = "windows_x86_64_gnullvm" 621 | version = "0.52.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 624 | 625 | [[package]] 626 | name = "windows_x86_64_msvc" 627 | version = "0.48.5" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 630 | 631 | [[package]] 632 | name = "windows_x86_64_msvc" 633 | version = "0.52.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 636 | 637 | [[package]] 638 | name = "zerocopy" 639 | version = "0.7.26" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" 642 | dependencies = [ 643 | "zerocopy-derive", 644 | ] 645 | 646 | [[package]] 647 | name = "zerocopy-derive" 648 | version = "0.7.26" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" 651 | dependencies = [ 652 | "proc-macro2", 653 | "quote", 654 | "syn 2.0.39", 655 | ] 656 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soda" 3 | authors = ["Sirui Mu "] 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.75" 11 | log = "0.4.20" 12 | object = { version = "0.32.1", features = ["read_core", "write_std", "elf"] } 13 | simple_logger = { version = "4.3.0", default-features = false, features = ["colors", "stderr"] } 14 | structopt = "0.3.26" 15 | thiserror = "1.0.50" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sirui Mu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # soda 2 | 3 | Convert shared libraries into static libraries. 4 | 5 | > [!NOTE] 6 | > This project is still under early development phase. 7 | 8 | ## Usage 9 | 10 | ```bash 11 | soda /path/to/your/libfoo.so 12 | ``` 13 | 14 | You can specify `-o` to change the output file name. If omitted, the default 15 | output file name will be `foo.o` if the input shared library is named 16 | `libfoo.so`. 17 | 18 | ## Build 19 | 20 | You need the latest stable Rust toolchain to build `soda`. Refer to [rustup] if 21 | you don't have a Rust toolchain yet. 22 | 23 | [rustup]: https://rustup.rs/ 24 | 25 | Clone this repository and build: 26 | 27 | ```bash 28 | git clone https://github.com/Lancern/soda.git 29 | cd soda 30 | cargo build 31 | ``` 32 | 33 | If you want to build a release version: 34 | 35 | ```bash 36 | cargo build --release 37 | ``` 38 | 39 | ## Contribution 40 | 41 | We welcome any form of contributions to this project: 42 | 43 | - Create a new [issue] for _bug reports_ and _feature request_. 44 | - Create a new [PR] for _bug fixes_ and _feature implementations_. 45 | - Create a new [discussion] if you have anything to share and discuss, or if you 46 | meet any problems in the usage of this tool. 47 | 48 | [issue]: https://github.com/Lancern/soda/issues 49 | [PR]: https://github.com/Lancern/soda/pulls 50 | [discussion]: https://github.com/Lancern/soda/discussions 51 | 52 | ## License 53 | 54 | This project is open-sourced under [MIT License](./LICENSE). 55 | -------------------------------------------------------------------------------- /src/elf/mod.rs: -------------------------------------------------------------------------------- 1 | mod pass; 2 | 3 | #[cfg(test)] 4 | mod test; 5 | 6 | use anyhow::anyhow; 7 | use object::read::elf::{ElfFile, FileHeader as ElfFileHeader}; 8 | use object::write::Object as OutputObject; 9 | use object::{Architecture, BinaryFormat, Endian, Endianness, Object as _, ObjectKind, ReadRef}; 10 | 11 | use crate::elf::pass::init_array::{GenerateFiniArrayPass, GenerateInitArrayPass}; 12 | use crate::elf::pass::reloc::ConvertRelocationPass; 13 | use crate::elf::pass::section::CopyLodableSectionsPass; 14 | use crate::elf::pass::symbol::GenerateSymbolPass; 15 | use crate::pass::PassManager; 16 | 17 | /// Convert the given ELF input shared library into an ELF relocatable file. 18 | pub fn convert<'d, E, R>(input: ElfFile<'d, E, R>) -> anyhow::Result> 19 | where 20 | E: ElfFileHeader, 21 | R: ReadRef<'d>, 22 | { 23 | assert_eq!(input.kind(), ObjectKind::Dynamic); 24 | 25 | let output = create_elf_output(&input)?; 26 | 27 | let mut pass_mgr = PassManager::new(); 28 | init_passes(&mut pass_mgr); 29 | 30 | let output = pass_mgr.run(input, output)?; 31 | Ok(output) 32 | } 33 | 34 | fn create_elf_output<'d, E, R>(input: &ElfFile<'d, E, R>) -> anyhow::Result> 35 | where 36 | E: ElfFileHeader, 37 | R: ReadRef<'d>, 38 | { 39 | const SUPPORTED_ARCH: &'static [Architecture] = &[Architecture::X86_64]; 40 | 41 | let endian = Endianness::from_big_endian(input.endian().is_big_endian()).unwrap(); 42 | let arch = input.architecture(); 43 | 44 | if !SUPPORTED_ARCH.contains(&arch) { 45 | return Err(anyhow!( 46 | "unsupported architecture: {}", 47 | crate::utils::stringify::arch_to_str(arch) 48 | )); 49 | } 50 | 51 | Ok(OutputObject::new(BinaryFormat::Elf, arch, endian)) 52 | } 53 | 54 | /// Register passes required to convert an ELF shared library. 55 | fn init_passes<'d, E, R>(pass_mgr: &mut PassManager>) 56 | where 57 | E: ElfFileHeader, 58 | R: ReadRef<'d>, 59 | { 60 | // Copy input sections to output sections. 61 | let cls_pass = pass_mgr.add_pass_default::(); 62 | 63 | // Copy the dynamic symbols in the input shared library into the normal symbols in the output relocatable object. 64 | let sym_gen_pass = pass_mgr.add_pass(GenerateSymbolPass { cls_pass }); 65 | 66 | // Convert the dynamic relocations in the input shared library to corresponding static relocations in the output 67 | // relocatable file. 68 | pass_mgr.add_pass(ConvertRelocationPass { 69 | cls_pass, 70 | sym_gen_pass, 71 | }); 72 | 73 | // Generate .init_array and .fini_array sections in the output relocatable file. 74 | pass_mgr.add_pass(GenerateInitArrayPass::new(cls_pass)); 75 | pass_mgr.add_pass(GenerateFiniArrayPass::new(cls_pass)); 76 | } 77 | -------------------------------------------------------------------------------- /src/elf/pass/init_array.rs: -------------------------------------------------------------------------------- 1 | use object::elf::{R_X86_64_RELATIVE, SHT_FINI_ARRAY, SHT_INIT_ARRAY}; 2 | use object::read::elf::{ElfFile, FileHeader as ElfFileHeader}; 3 | use object::write::{Relocation as OutputRelocation, SymbolId}; 4 | use object::{ 5 | Architecture, Object as _, ObjectSection as _, ReadRef, Relocation, RelocationKind, SectionKind, 6 | }; 7 | use thiserror::Error; 8 | 9 | use crate::elf::pass::section::CopyLodableSectionsPass; 10 | use crate::pass::{Pass, PassContext, PassHandle}; 11 | 12 | /// Generate a .init_array section in the output relocatable file. 13 | #[derive(Debug)] 14 | pub struct GenerateInitArrayPass { 15 | inner: GenerateFuncPtrArray, 16 | } 17 | 18 | impl GenerateInitArrayPass { 19 | pub fn new(cls_pass: PassHandle) -> Self { 20 | Self { 21 | inner: GenerateFuncPtrArray::new(cls_pass), 22 | } 23 | } 24 | } 25 | 26 | impl<'d, E, R> Pass> for GenerateInitArrayPass 27 | where 28 | E: ElfFileHeader, 29 | R: ReadRef<'d>, 30 | { 31 | const NAME: &'static str = "generate init array"; 32 | 33 | type Output = (); 34 | type Error = GenerateInitFiniArrayError; 35 | 36 | fn run(&mut self, ctx: &PassContext>) -> Result { 37 | self.inner.generate(ctx, SHT_INIT_ARRAY) 38 | } 39 | } 40 | 41 | /// Generate a .fini_array section in the output relocatable file. 42 | #[derive(Debug)] 43 | pub struct GenerateFiniArrayPass { 44 | inner: GenerateFuncPtrArray, 45 | } 46 | 47 | impl GenerateFiniArrayPass { 48 | pub fn new(cls_pass: PassHandle) -> Self { 49 | Self { 50 | inner: GenerateFuncPtrArray::new(cls_pass), 51 | } 52 | } 53 | } 54 | 55 | impl<'d, E, R> Pass> for GenerateFiniArrayPass 56 | where 57 | E: ElfFileHeader, 58 | R: ReadRef<'d>, 59 | { 60 | const NAME: &'static str = "generate fini array"; 61 | 62 | type Output = (); 63 | type Error = GenerateInitFiniArrayError; 64 | 65 | fn run(&mut self, ctx: &PassContext>) -> Result { 66 | self.inner.generate(ctx, SHT_FINI_ARRAY) 67 | } 68 | } 69 | 70 | #[derive(Debug, Error)] 71 | pub enum GenerateInitFiniArrayError { 72 | #[error("unsupported architecture: {0:?}")] 73 | UnsupportedArch(Architecture), 74 | 75 | #[error("unsupported reloc: {0:?}")] 76 | UnsupportedReloc(RelocationKind), 77 | } 78 | 79 | #[derive(Debug)] 80 | struct GenerateFuncPtrArray { 81 | cls_pass: PassHandle, 82 | } 83 | 84 | impl GenerateFuncPtrArray { 85 | fn new(cls_pass: PassHandle) -> Self { 86 | Self { cls_pass } 87 | } 88 | 89 | fn generate<'d, E, R>( 90 | &self, 91 | ctx: &PassContext>, 92 | sec_type: u32, 93 | ) -> Result<(), GenerateInitFiniArrayError> 94 | where 95 | E: ElfFileHeader, 96 | R: ReadRef<'d>, 97 | { 98 | assert!(sec_type == SHT_INIT_ARRAY || sec_type == SHT_FINI_ARRAY); 99 | let output_sec_name = match sec_type { 100 | SHT_INIT_ARRAY => ".init_array", 101 | SHT_FINI_ARRAY => ".fini_array", 102 | _ => unreachable!(), 103 | }; 104 | 105 | let cls_output = ctx.get_pass_output(self.cls_pass); 106 | 107 | let input_sections = ctx 108 | .input 109 | .sections() 110 | .filter(|sec| sec.kind() == SectionKind::Elf(sec_type)); 111 | 112 | let arch = ctx.input.architecture(); 113 | let mut output_sec_size = 0; 114 | let mut output_relocs = Vec::new(); 115 | 116 | for input_sec in input_sections { 117 | if !cls_output.is_section_copied(input_sec.index()) { 118 | continue; 119 | } 120 | 121 | let input_sec_size = input_sec.size(); 122 | if input_sec_size == 0 { 123 | continue; 124 | } 125 | 126 | output_sec_size += input_sec_size; 127 | 128 | let input_sec_addr = input_sec.address(); 129 | let input_sec_addr_range = input_sec_addr..input_sec_addr + input_sec_size; 130 | 131 | // Find all input relocations associated with the input section and convert them to corresponding output 132 | // relocations associated with the output section. 133 | 134 | for (input_reloc_addr, input_reloc) in ctx.input.dynamic_relocations().unwrap() { 135 | if !input_sec_addr_range.contains(&input_reloc_addr) { 136 | continue; 137 | } 138 | 139 | let output_reloc = convert_init_fini_array_reloc( 140 | arch, 141 | input_reloc_addr, 142 | &input_reloc, 143 | cls_output.output_section_symbol, 144 | )?; 145 | output_relocs.push(output_reloc); 146 | } 147 | } 148 | 149 | if output_sec_size == 0 { 150 | return Ok(()); 151 | } 152 | 153 | let mut output = ctx.output.borrow_mut(); 154 | let output_sec_id = output.add_section( 155 | Vec::new(), 156 | output_sec_name.as_bytes().to_vec(), 157 | SectionKind::Elf(sec_type), 158 | ); 159 | 160 | const INIT_FINI_ARRAY_ALIGN: u64 = 8; 161 | output.set_section_data( 162 | output_sec_id, 163 | vec![0u8; output_sec_size as usize], 164 | INIT_FINI_ARRAY_ALIGN, 165 | ); 166 | 167 | for r in output_relocs { 168 | output.add_relocation(output_sec_id, r).unwrap(); 169 | } 170 | 171 | Ok(()) 172 | } 173 | } 174 | 175 | fn convert_init_fini_array_reloc( 176 | arch: Architecture, 177 | input_reloc_addr: u64, 178 | input_reloc: &Relocation, 179 | output_main_sec_sym: SymbolId, 180 | ) -> Result { 181 | match arch { 182 | Architecture::X86_64 => { 183 | convert_init_fini_array_reloc_x86_64(input_reloc_addr, input_reloc, output_main_sec_sym) 184 | } 185 | arch => Err(GenerateInitFiniArrayError::UnsupportedArch(arch)), 186 | } 187 | } 188 | 189 | fn convert_init_fini_array_reloc_x86_64( 190 | input_reloc_addr: u64, 191 | input_reloc: &Relocation, 192 | output_main_sec_sym: SymbolId, 193 | ) -> Result { 194 | let output_reloc = match input_reloc.kind() { 195 | RelocationKind::Elf(R_X86_64_RELATIVE) => OutputRelocation { 196 | offset: input_reloc_addr, 197 | size: 64, 198 | kind: RelocationKind::Absolute, 199 | encoding: input_reloc.encoding(), 200 | symbol: output_main_sec_sym, 201 | addend: input_reloc.addend(), 202 | }, 203 | kind => { 204 | return Err(GenerateInitFiniArrayError::UnsupportedReloc(kind)); 205 | } 206 | }; 207 | Ok(output_reloc) 208 | } 209 | -------------------------------------------------------------------------------- /src/elf/pass/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod init_array; 2 | pub mod reloc; 3 | pub mod section; 4 | pub mod symbol; 5 | -------------------------------------------------------------------------------- /src/elf/pass/reloc.rs: -------------------------------------------------------------------------------- 1 | use object::elf::{ 2 | R_X86_64_64, R_X86_64_DTPMOD64, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, R_X86_64_RELATIVE, 3 | }; 4 | use object::read::elf::{ElfFile, FileHeader as ElfFileHeader}; 5 | use object::read::Error as ReadError; 6 | use object::write::Relocation as OutputRelocation; 7 | use object::{Architecture, Object as _, ReadRef, RelocationKind, RelocationTarget}; 8 | use thiserror::Error; 9 | 10 | use crate::elf::pass::section::CopyLodableSectionsPass; 11 | use crate::elf::pass::symbol::GenerateSymbolPass; 12 | use crate::pass::{Pass, PassContext, PassHandle}; 13 | 14 | /// A pass that converts the dynamic relocations in the input shared library into corresponding static relocations in 15 | /// the output relocatable file. 16 | #[derive(Debug)] 17 | pub struct ConvertRelocationPass { 18 | pub cls_pass: PassHandle, 19 | pub sym_gen_pass: PassHandle, 20 | } 21 | 22 | impl ConvertRelocationPass { 23 | fn convert_x86_64_relocations<'d, E, R>( 24 | &self, 25 | ctx: &PassContext>, 26 | ) -> Result<(), ConvertRelocationError> 27 | where 28 | E: ElfFileHeader, 29 | R: ReadRef<'d>, 30 | { 31 | assert_eq!(ctx.input.architecture(), Architecture::X86_64); 32 | 33 | let input_reloc_iter = match ctx.input.dynamic_relocations() { 34 | Some(iter) => iter, 35 | None => { 36 | return Ok(()); 37 | } 38 | }; 39 | 40 | let cls_output = ctx.get_pass_output(self.cls_pass); 41 | let sym_map = ctx.get_pass_output(self.sym_gen_pass); 42 | 43 | let mut output = ctx.output.borrow_mut(); 44 | 45 | for (input_reloc_addr, input_reloc) in input_reloc_iter { 46 | if input_reloc_addr >= cls_output.output_section_size { 47 | log::warn!("Relocation happens outside of loadable sections"); 48 | continue; 49 | } 50 | 51 | if input_reloc.size() != 0 && input_reloc.size() != 64 { 52 | log::warn!("Unexpected relocation size"); 53 | } 54 | 55 | let output_reloc_offset = input_reloc_addr; 56 | 57 | let output_reloc = match input_reloc.kind() { 58 | RelocationKind::Elf(R_X86_64_RELATIVE) => OutputRelocation { 59 | offset: output_reloc_offset, 60 | size: 64, 61 | kind: RelocationKind::Absolute, 62 | encoding: input_reloc.encoding(), 63 | symbol: cls_output.output_section_symbol, 64 | addend: input_reloc.addend(), 65 | }, 66 | 67 | RelocationKind::Absolute 68 | | RelocationKind::Elf(R_X86_64_64) 69 | | RelocationKind::Elf(R_X86_64_GLOB_DAT) 70 | | RelocationKind::Elf(R_X86_64_JUMP_SLOT) => { 71 | let target_sym_idx = match input_reloc.target() { 72 | RelocationTarget::Symbol(sym_idx) => sym_idx, 73 | _ => todo!(), 74 | }; 75 | let output_sym_id = sym_map.get_output_symbol(target_sym_idx).unwrap(); 76 | OutputRelocation { 77 | offset: output_reloc_offset, 78 | size: 64, 79 | kind: RelocationKind::Absolute, 80 | encoding: input_reloc.encoding(), 81 | symbol: output_sym_id, 82 | addend: input_reloc.addend(), 83 | } 84 | } 85 | 86 | RelocationKind::Elf(R_X86_64_DTPMOD64) => OutputRelocation { 87 | offset: output_reloc_offset, 88 | size: 64, 89 | kind: RelocationKind::Elf(R_X86_64_DTPMOD64), 90 | encoding: input_reloc.encoding(), 91 | symbol: cls_output.output_section_symbol, // TODO: no symbols should be associated with this reloc. 92 | addend: input_reloc.addend(), 93 | }, 94 | 95 | kind => { 96 | return Err(ConvertRelocationError::UnsupportedReloc(kind)); 97 | } 98 | }; 99 | 100 | output 101 | .add_relocation(cls_output.output_section_id, output_reloc) 102 | .unwrap(); 103 | } 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl<'d, E, R> Pass> for ConvertRelocationPass 110 | where 111 | E: ElfFileHeader, 112 | R: ReadRef<'d>, 113 | { 114 | const NAME: &'static str = "convert relocations"; 115 | 116 | type Output = (); 117 | type Error = ConvertRelocationError; 118 | 119 | fn run(&mut self, ctx: &PassContext>) -> Result 120 | where 121 | E: ElfFileHeader, 122 | R: ReadRef<'d>, 123 | { 124 | match ctx.input.architecture() { 125 | Architecture::X86_64 => { 126 | self.convert_x86_64_relocations(ctx)?; 127 | } 128 | arch => { 129 | return Err(ConvertRelocationError::UnsupportedArch(arch)); 130 | } 131 | } 132 | 133 | Ok(()) 134 | } 135 | } 136 | 137 | /// Errors that may occur when converting input relocations. 138 | #[derive(Debug, Error)] 139 | pub enum ConvertRelocationError { 140 | #[error("read ELF failed: {0:?}")] 141 | ReadElfError(#[from] ReadError), 142 | 143 | #[error("unsupported architecture: {0:?}")] 144 | UnsupportedArch(Architecture), 145 | 146 | #[error("unsupported reloc: {0:?}")] 147 | UnsupportedReloc(RelocationKind), 148 | } 149 | 150 | #[cfg(test)] 151 | mod test { 152 | use object::read::elf::ElfFile64; 153 | use object::write::Object as OutputObject; 154 | use object::{Architecture, BinaryFormat, Endianness}; 155 | 156 | use crate::elf::pass::section::CopyLodableSectionsPass; 157 | use crate::elf::pass::symbol::GenerateSymbolPass; 158 | use crate::pass::test::PassTest; 159 | use crate::pass::{PassHandle, PassManager}; 160 | 161 | use super::ConvertRelocationPass; 162 | 163 | struct ConvertRelocationPassTest; 164 | 165 | impl PassTest for ConvertRelocationPassTest { 166 | type Input = ElfFile64<'static>; 167 | type Pass = ConvertRelocationPass; 168 | 169 | fn setup(&mut self, pass_mgr: &mut PassManager) -> PassHandle { 170 | let cls_pass = pass_mgr.add_pass_default::(); 171 | let sym_gen_pass = pass_mgr.add_pass(GenerateSymbolPass { cls_pass }); 172 | pass_mgr.add_pass(ConvertRelocationPass { 173 | cls_pass, 174 | sym_gen_pass, 175 | }) 176 | } 177 | } 178 | 179 | #[test] 180 | fn test_convert_relocation_pass() { 181 | let input = crate::elf::test::get_test_input_file(); 182 | let output = OutputObject::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); 183 | crate::pass::test::run_pass_test(ConvertRelocationPassTest, input, output); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/elf/pass/section.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use object::elf::{PT_LOAD, SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_PROGBITS}; 4 | use object::read::elf::{ 5 | ElfFile, ElfSection, ElfSegment, FileHeader as ElfFileHeader, ProgramHeader as _, 6 | }; 7 | use object::read::Error as ReadError; 8 | use object::write::{SectionId, SymbolId}; 9 | use object::{ 10 | Object, ObjectSection, ObjectSegment, ReadRef, SectionFlags, SectionIndex, SectionKind, 11 | }; 12 | 13 | use crate::pass::{Pass, PassContext}; 14 | 15 | /// A pass that copies loadable sections in the input shared library into the output relocatable object. 16 | /// 17 | /// All such input sections will be copied into the same section in the output relocatable object so that internal 18 | /// references won't break in further linking. 19 | #[derive(Debug, Default)] 20 | pub struct CopyLodableSectionsPass; 21 | 22 | impl<'d, E, R> Pass> for CopyLodableSectionsPass 23 | where 24 | E: ElfFileHeader, 25 | R: ReadRef<'d>, 26 | { 27 | const NAME: &'static str = "copy sections"; 28 | 29 | type Output = CopyLodableSectionsOutput; 30 | type Error = ReadError; 31 | 32 | fn run(&mut self, ctx: &PassContext>) -> Result 33 | where 34 | E: ElfFileHeader, 35 | R: ReadRef<'d>, 36 | { 37 | let mut output = ctx.output.borrow_mut(); 38 | 39 | // TODO: make the output section's name customizable. 40 | let output_sec_id = output.add_section( 41 | Vec::new(), 42 | ".soda".as_bytes().to_vec(), 43 | SectionKind::Elf(SHT_PROGBITS), 44 | ); 45 | let output_sec_sym = output.section_symbol(output_sec_id); 46 | let output_sec = output.section_mut(output_sec_id); 47 | 48 | let mut ret = CopyLodableSectionsOutput { 49 | output_section_id: output_sec_id, 50 | output_section_symbol: output_sec_sym, 51 | output_section_size: 0, 52 | section_maps: Vec::new(), 53 | }; 54 | 55 | // First we collect all loadable sections. The returned section list is sorted by their base addresses. 56 | let input_sections = collect_loadable_sections(&ctx.input); 57 | if input_sections.is_empty() { 58 | return Ok(ret); 59 | } 60 | 61 | output_sec.flags = get_output_section_flags(&input_sections); 62 | 63 | // Copy the data of the collected input sections to the output section. 64 | // First calculate the size and alignment of the output section, together with the offset of each input section 65 | // in the output section. 66 | let mut output_sec_size = 0u64; 67 | for input_sec in &input_sections { 68 | let input_sec_name = String::from_utf8_lossy(input_sec.name_bytes()?); 69 | 70 | let input_sec_addr = input_sec.address(); 71 | let input_sec_size = input_sec.size(); 72 | let input_sec_align = input_sec.align(); 73 | 74 | if input_sec_addr < output_sec_size { 75 | log::warn!( 76 | "Overlapping section \"{}\" (section index {})", 77 | input_sec_name, 78 | input_sec.index().0 79 | ); 80 | } 81 | if input_sec_align != 0 && input_sec_addr % input_sec_align != 0 { 82 | log::warn!( 83 | "Unaligned input section \"{}\" (section index {})", 84 | input_sec_name, 85 | input_sec.index().0 86 | ); 87 | } 88 | 89 | let input_sec_end = input_sec_addr.checked_add(input_sec_size).unwrap(); 90 | output_sec_size = input_sec_end; 91 | ret.section_maps.push(SectionMap { 92 | index: input_sec.index(), 93 | addr_range: input_sec_addr..input_sec_end, 94 | }); 95 | } 96 | 97 | assert!(output_sec_size <= std::usize::MAX as u64); 98 | ret.output_section_size = output_sec_size; 99 | 100 | // Calculate the alignment of the output section. 101 | let output_sec_align = input_sections.iter().map(|sec| sec.align()).max().unwrap(); 102 | 103 | // Then do the data copy. 104 | let mut output_buffer = vec![0u8; output_sec_size as usize]; 105 | for input_sec in &input_sections { 106 | let sec_data = input_sec.uncompressed_data()?; 107 | assert!(sec_data.len() <= input_sec.size() as usize); 108 | 109 | if sec_data.is_empty() { 110 | continue; 111 | } 112 | 113 | let input_sec_addr = input_sec.address(); 114 | let output_range = input_sec_addr as usize..input_sec_addr as usize + sec_data.len(); 115 | 116 | let output_slice = &mut output_buffer[output_range]; 117 | output_slice.copy_from_slice(&sec_data); 118 | } 119 | 120 | // Set the output section's data. 121 | output_sec.set_data(output_buffer, output_sec_align); 122 | 123 | Ok(ret) 124 | } 125 | } 126 | 127 | fn collect_loadable_sections<'d, 'f, E, R>( 128 | input: &'f ElfFile<'d, E, R>, 129 | ) -> Vec> 130 | where 131 | E: ElfFileHeader, 132 | R: ReadRef<'d>, 133 | { 134 | let endian = input.endian(); 135 | let mut input_sections = Vec::new(); 136 | 137 | for (seg_header, seg) in input.raw_segments().iter().zip(input.segments()) { 138 | let seg_type = seg_header.p_type(endian); 139 | if seg_type != PT_LOAD { 140 | // Skip sections in non-loadable segments. 141 | continue; 142 | } 143 | 144 | // Enumerate all sections in the current segment which is loadable. 145 | for input_sec in input.sections() { 146 | // We don't deal with the UND section. (i.e. the section at index 0) 147 | if input_sec.index().0 == 0 { 148 | continue; 149 | } 150 | 151 | if input_sec.address() == 0 { 152 | // Input sections whose address is 0 are not included in the memory image. 153 | continue; 154 | } 155 | 156 | if !is_section_in_segment(&input_sec, &seg) { 157 | continue; 158 | } 159 | 160 | input_sections.push(input_sec); 161 | } 162 | } 163 | 164 | input_sections.sort_by_key(|sec| sec.address()); 165 | input_sections 166 | } 167 | 168 | fn get_output_section_flags<'d, 'f, E, R>( 169 | input_sections: &[ElfSection<'d, 'f, E, R>], 170 | ) -> SectionFlags 171 | where 172 | E: ElfFileHeader, 173 | R: ReadRef<'d>, 174 | { 175 | let mut writable = false; 176 | let mut executable = false; 177 | 178 | for input_sec in input_sections { 179 | let sec_flags = match input_sec.flags() { 180 | SectionFlags::Elf { sh_flags } => sh_flags, 181 | _ => unreachable!(), 182 | }; 183 | writable |= sec_flags & SHF_WRITE as u64 != 0; 184 | executable |= sec_flags & SHF_EXECINSTR as u64 != 0; 185 | } 186 | 187 | let mut raw_flags = SHF_ALLOC; 188 | if writable { 189 | raw_flags |= SHF_WRITE; 190 | } 191 | if executable { 192 | raw_flags |= SHF_EXECINSTR; 193 | } 194 | 195 | SectionFlags::Elf { 196 | sh_flags: raw_flags as u64, 197 | } 198 | } 199 | 200 | #[derive(Debug)] 201 | pub struct CopyLodableSectionsOutput { 202 | /// The section ID of the output section. 203 | pub output_section_id: SectionId, 204 | 205 | /// The ID of the output section symbol. 206 | pub output_section_symbol: SymbolId, 207 | 208 | /// Size of the output section. 209 | pub output_section_size: u64, 210 | 211 | /// Gives the information about copied sections. 212 | pub section_maps: Vec, 213 | } 214 | 215 | impl CopyLodableSectionsOutput { 216 | /// Determine whether the specified input section is copied into the output section. 217 | pub fn is_section_copied(&self, idx: SectionIndex) -> bool { 218 | self.get_section_map(idx).is_some() 219 | } 220 | 221 | fn get_section_map(&self, section_idx: SectionIndex) -> Option<&SectionMap> { 222 | self.section_maps 223 | .iter() 224 | .find(|map| map.index == section_idx) 225 | } 226 | } 227 | 228 | #[derive(Clone, Debug)] 229 | #[cfg_attr(test, derive(Eq, PartialEq))] 230 | pub struct SectionMap { 231 | pub index: SectionIndex, 232 | pub addr_range: Range, 233 | } 234 | 235 | fn is_section_in_segment<'d, 'f, E, R>( 236 | sec: &ElfSection<'d, 'f, E, R>, 237 | seg: &ElfSegment<'d, 'f, E, R>, 238 | ) -> bool 239 | where 240 | E: ElfFileHeader, 241 | R: ReadRef<'d>, 242 | { 243 | let sec_addr = sec.address(); 244 | let seg_addr = seg.address(); 245 | 246 | let sec_size = sec.size(); 247 | let seg_size = seg.size(); 248 | 249 | let sec_end_addr = sec_addr + sec_size; 250 | let seg_end_addr = seg_addr + seg_size; 251 | 252 | sec_addr >= seg_addr && sec_end_addr <= seg_end_addr 253 | } 254 | 255 | #[cfg(test)] 256 | mod test { 257 | use std::ops::Range; 258 | 259 | use object::read::elf::ElfFile64; 260 | use object::read::SectionIndex; 261 | use object::write::Object as OutputObject; 262 | use object::{Architecture, BinaryFormat, Endianness}; 263 | 264 | use crate::pass::test::PassTest; 265 | use crate::pass::{Pass, PassHandle, PassManager}; 266 | 267 | use super::{CopyLodableSectionsPass, SectionMap}; 268 | 269 | struct CopyLoadableSectionPassTest; 270 | 271 | impl PassTest for CopyLoadableSectionPassTest { 272 | type Input = ElfFile64<'static>; 273 | type Pass = CopyLodableSectionsPass; 274 | 275 | fn setup(&mut self, pass_mgr: &mut PassManager) -> PassHandle { 276 | pass_mgr.add_pass_default::() 277 | } 278 | 279 | fn check_pass_output(&mut self, output: &>::Output) { 280 | fn addr_range(addr: u64, size: u64) -> Range { 281 | addr..addr + size 282 | } 283 | 284 | macro_rules! make_section_maps { 285 | ( $( { $index:expr, $addr:expr, $size:expr $(,)? } ),* $(,)? ) => { 286 | vec![ 287 | $( 288 | SectionMap { 289 | index: SectionIndex($index), 290 | addr_range: addr_range($addr, $size), 291 | } 292 | ),* 293 | ] 294 | }; 295 | } 296 | 297 | assert_eq!(output.output_section_size, 0x95e28); 298 | assert_eq!( 299 | output.section_maps, 300 | make_section_maps! { 301 | { 1, 0x2e0, 0x30 }, 302 | { 2, 0x310, 0x24 }, 303 | { 3, 0x338, 0x2910 }, 304 | { 4, 0x2c48, 0x8a48 }, 305 | { 5, 0xb690, 0x1cb3f }, 306 | { 6, 0x281d0, 0xb86 }, 307 | { 7, 0x28d58, 0x180 }, 308 | { 8, 0x28ed8, 0x7320 }, 309 | { 9, 0x301f8, 0x2280 }, 310 | { 10, 0x33000, 0x1b }, 311 | { 11, 0x33020, 0x1710 }, 312 | { 12, 0x34730, 0x28 }, 313 | { 13, 0x34760, 0x4a4a4 }, 314 | { 14, 0x7ec04, 0xd }, 315 | { 15, 0x7f000, 0x4d70 }, 316 | { 16, 0x83d70, 0x1b5c }, 317 | { 17, 0x858d0, 0x9804 }, 318 | { 18, 0x8f0d4, 0x2234 }, 319 | { 19, 0x92390, 0x10 }, 320 | { 20, 0x92390, 0x8 }, 321 | { 21, 0x92398, 0x8 }, 322 | { 22, 0x923a0, 0x2490 }, 323 | { 23, 0x94830, 0x210 }, 324 | { 24, 0x94a40, 0x598 }, 325 | { 25, 0x94fe8, 0xb98 }, 326 | { 26, 0x95b80, 0xa0 }, 327 | { 27, 0x95c20, 0x208 }, 328 | } 329 | ); 330 | } 331 | } 332 | 333 | #[test] 334 | fn test_cls_pass() { 335 | let input = crate::elf::test::get_test_input_file(); 336 | let output = OutputObject::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); 337 | crate::pass::test::run_pass_test(CopyLoadableSectionPassTest, input, output); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/elf/pass/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use object::elf::{STB_GLOBAL, STB_GNU_UNIQUE, STB_LOCAL}; 4 | use object::read::elf::{ElfFile, ElfSymbol, FileHeader as ElfFileHeader}; 5 | use object::read::Error as ReadError; 6 | use object::write::{Symbol as OutputSymbol, SymbolId, SymbolSection as OutputSymbolSection}; 7 | use object::{Object, ObjectSymbol, ReadRef, SymbolFlags, SymbolIndex, SymbolScope, SymbolSection}; 8 | 9 | use crate::elf::pass::section::{CopyLodableSectionsOutput, CopyLodableSectionsPass}; 10 | use crate::pass::{Pass, PassContext, PassHandle}; 11 | 12 | /// A pass that generates the symbol table of the output relocatable file. 13 | /// 14 | /// This pass generates the symbol table based on the dynamic symbols of the input shared library. Specifically, for 15 | /// each dynamic symbol in the input shared library whose containing section is included in the output relocatable file, 16 | /// a corresponding symbol will be generated in the output relocatable file's symbol table: 17 | /// 18 | /// - Undefined input symbol will generate a corresponding undefined output symbol; 19 | /// - Defined local symbol will generate a corresponding defined local symbol; 20 | /// - Defined external symbol will generate a corresponding defined external symbol. 21 | /// 22 | /// This pass will produce a symbol map that maps input dynamic symbols to output symbols. 23 | #[derive(Debug)] 24 | pub struct GenerateSymbolPass { 25 | pub cls_pass: PassHandle, 26 | } 27 | 28 | impl<'d, E, R> Pass> for GenerateSymbolPass 29 | where 30 | E: ElfFileHeader, 31 | R: ReadRef<'d>, 32 | { 33 | const NAME: &'static str = "generate symbols"; 34 | 35 | type Output = SymbolMap; 36 | type Error = ReadError; 37 | 38 | fn run(&mut self, ctx: &PassContext>) -> Result 39 | where 40 | E: ElfFileHeader, 41 | R: ReadRef<'d>, 42 | { 43 | let mut output = ctx.output.borrow_mut(); 44 | 45 | let cls_output = ctx.get_pass_output(self.cls_pass); 46 | 47 | let mut sym_map = HashMap::new(); 48 | for input_sym in ctx.input.dynamic_symbols() { 49 | // Ensure that the section containing the symbol has been copied into the output relocatable file. If not, 50 | // such symbols will not cause the generation of an output symbol. 51 | if let Some(sym_section_idx) = input_sym.section_index() { 52 | if !cls_output.is_section_copied(sym_section_idx) { 53 | continue; 54 | } 55 | } 56 | 57 | let output_sym = create_output_symbol(&input_sym, cls_output)?; 58 | let output_sym_id = output.add_symbol(output_sym); 59 | sym_map.insert(input_sym.index(), output_sym_id); 60 | } 61 | 62 | Ok(SymbolMap(sym_map)) 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct SymbolMap(HashMap); 68 | 69 | impl SymbolMap { 70 | /// Get the output symbol corresponding to the specified input symbol. 71 | pub fn get_output_symbol(&self, input_sym: SymbolIndex) -> Option { 72 | self.0.get(&input_sym).copied() 73 | } 74 | } 75 | 76 | fn create_output_symbol<'d, 'f, E, R>( 77 | input_sym: &ElfSymbol<'d, 'f, E, R>, 78 | copied_sections: &CopyLodableSectionsOutput, 79 | ) -> Result 80 | where 81 | E: ElfFileHeader, 82 | R: ReadRef<'d>, 83 | { 84 | let name = input_sym.name_bytes()?.to_vec(); 85 | 86 | let section = match input_sym.section() { 87 | SymbolSection::None => OutputSymbolSection::None, 88 | SymbolSection::Undefined => OutputSymbolSection::Undefined, 89 | SymbolSection::Absolute => OutputSymbolSection::Absolute, 90 | SymbolSection::Common => OutputSymbolSection::Common, 91 | SymbolSection::Section(sec_idx) => { 92 | assert!(copied_sections.is_section_copied(sec_idx)); 93 | OutputSymbolSection::Section(copied_sections.output_section_id) 94 | } 95 | _ => unreachable!(), 96 | }; 97 | 98 | let (mut st_info, st_other) = match input_sym.flags() { 99 | SymbolFlags::Elf { st_info, st_other } => (st_info, st_other), 100 | _ => unreachable!(), 101 | }; 102 | 103 | let mut bind = st_info >> 4; 104 | if bind == STB_GNU_UNIQUE { 105 | bind = STB_GLOBAL; 106 | st_info = (STB_GLOBAL << 4) | (st_info & 0xF); 107 | } 108 | 109 | let scope = match input_sym.scope() { 110 | SymbolScope::Unknown => { 111 | if bind == STB_LOCAL { 112 | SymbolScope::Compilation 113 | } else { 114 | SymbolScope::Linkage 115 | } 116 | } 117 | SymbolScope::Dynamic => SymbolScope::Linkage, 118 | scope => scope, 119 | }; 120 | 121 | Ok(OutputSymbol { 122 | name, 123 | value: input_sym.address(), 124 | size: input_sym.size(), 125 | kind: input_sym.kind(), 126 | scope, 127 | weak: input_sym.is_weak(), 128 | section, 129 | flags: SymbolFlags::Elf { st_info, st_other }, 130 | }) 131 | } 132 | 133 | #[cfg(test)] 134 | mod test { 135 | use object::read::elf::ElfFile64; 136 | use object::write::Object as OutputObject; 137 | use object::{Architecture, BinaryFormat, Endianness}; 138 | 139 | use crate::elf::pass::section::CopyLodableSectionsPass; 140 | use crate::pass::test::PassTest; 141 | use crate::pass::{Pass, PassHandle, PassManager}; 142 | 143 | use super::GenerateSymbolPass; 144 | 145 | struct GenerateSymbolPassTest; 146 | 147 | impl PassTest for GenerateSymbolPassTest { 148 | type Input = ElfFile64<'static>; 149 | type Pass = GenerateSymbolPass; 150 | 151 | fn setup(&mut self, pass_mgr: &mut PassManager) -> PassHandle { 152 | let cls_pass = pass_mgr.add_pass_default::(); 153 | pass_mgr.add_pass(GenerateSymbolPass { cls_pass }) 154 | } 155 | 156 | fn check_pass_output(&mut self, output: &>::Output) { 157 | assert_eq!(output.0.len(), 1475); 158 | } 159 | } 160 | 161 | #[test] 162 | fn test_generate_symbol_pass() { 163 | let input = crate::elf::test::get_test_input_file(); 164 | let output = OutputObject::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); 165 | crate::pass::test::run_pass_test(GenerateSymbolPassTest, input, output); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/elf/test/libspdlog.so.1.12.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lancern/soda/38781445f92d6e363798b4bfe1ecd407638ad05f/src/elf/test/libspdlog.so.1.12.0 -------------------------------------------------------------------------------- /src/elf/test/mod.rs: -------------------------------------------------------------------------------- 1 | use object::read::elf::ElfFile64; 2 | 3 | pub fn get_test_input_file() -> ElfFile64<'static> { 4 | let file_data = include_bytes!("libspdlog.so.1.12.0").as_slice(); 5 | ElfFile64::parse(file_data).unwrap() 6 | } 7 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod elf; 2 | mod pass; 3 | mod utils; 4 | 5 | use std::borrow::Cow; 6 | use std::fs::File; 7 | use std::io::BufWriter; 8 | use std::path::{Path, PathBuf}; 9 | use std::process::ExitCode; 10 | 11 | use anyhow::{anyhow, Context as _}; 12 | use log::{Level as LogLevel, SetLoggerError}; 13 | use object::read::{File as InputFile, ObjectKind}; 14 | use object::Object as _; 15 | use structopt::StructOpt; 16 | 17 | #[derive(Clone, Debug, StructOpt)] 18 | #[structopt( 19 | name = "soda", 20 | author = "Sirui Mu ", 21 | about = "Convert shared libraries into static libraries" 22 | )] 23 | struct Args { 24 | /// Path to the input shared library. 25 | #[structopt(parse(from_os_str))] 26 | input: PathBuf, 27 | 28 | /// Path to the output relocatable object file. 29 | #[structopt(short, long)] 30 | #[structopt(parse(from_os_str))] 31 | output: Option, 32 | 33 | /// Output verbosity. 34 | #[structopt(short, parse(from_occurrences))] 35 | verbosity: u8, 36 | } 37 | 38 | impl Args { 39 | fn get_output_path(&self) -> Cow { 40 | if let Some(path) = &self.output { 41 | return Cow::Borrowed(path); 42 | } 43 | 44 | // If the user does not provide an output path, we form one by replacing the file name part of the input path 45 | // with a proper static library name. 46 | // 47 | // Examples of name conversion: 48 | // - `/dir/libxyz.so` will be converted to `/dir/xyz.o` 49 | // - `/dir/xyz.so` will be converted to `/dir/xyz.o` 50 | 51 | let mut path = self.input.clone(); 52 | let file_name = path.file_name().unwrap().to_str().unwrap(); 53 | path.set_file_name(convert_soname_to_object_name(file_name)); 54 | 55 | Cow::Owned(path) 56 | } 57 | } 58 | 59 | fn main() -> ExitCode { 60 | let args = Args::from_args(); 61 | if let Err(err) = do_main(&args) { 62 | eprintln!("Error: {:#}", err); 63 | return ExitCode::FAILURE; 64 | } 65 | 66 | ExitCode::SUCCESS 67 | } 68 | 69 | fn do_main(args: &Args) -> anyhow::Result<()> { 70 | init_logger(args.verbosity)?; 71 | 72 | log::info!("Reading input shared library ..."); 73 | let input_buffer = std::fs::read(&args.input).context(format!( 74 | "cannot read input shared library \"{}\"", 75 | args.input.display() 76 | ))?; 77 | let input_file = InputFile::parse(input_buffer.as_slice()).context(format!( 78 | "cannot parse input shared library \"{}\"", 79 | args.input.display() 80 | ))?; 81 | 82 | if input_file.kind() != ObjectKind::Dynamic { 83 | return Err(anyhow::Error::msg("input file is not a shared library")); 84 | } 85 | 86 | // Open the output file, preparing to write later. 87 | let output_path = &*args.get_output_path(); 88 | let mut output_file = OutputFile::create(output_path).context(format!( 89 | "failed to open output file \"{}\"", 90 | output_path.display() 91 | ))?; 92 | 93 | // Convert the input shared library into output relocatable file. 94 | log::info!("Start the conversion"); 95 | let output_object = match input_file { 96 | InputFile::Elf32(elf_file) => crate::elf::convert(elf_file)?, 97 | InputFile::Elf64(elf_file) => crate::elf::convert(elf_file)?, 98 | _ => { 99 | return Err(anyhow!( 100 | "{} format is not supported yet", 101 | crate::utils::stringify::binary_format_to_str(input_file.format()) 102 | )); 103 | } 104 | }; 105 | 106 | // Save the produced output object to the output file. 107 | log::info!("Writing output file ..."); 108 | output_object 109 | .write_stream(output_file.writer()) 110 | .map_err(|err| anyhow!("{:?}", err)) 111 | .context(format!( 112 | "failed to write output file \"{}\"", 113 | output_path.display() 114 | ))?; 115 | 116 | output_file.prevent_delete_on_drop(); 117 | log::info!("Done."); 118 | 119 | Ok(()) 120 | } 121 | 122 | /// Convert a shared library name into its corresponding object name. 123 | /// 124 | /// Examples of the conversion: 125 | /// - `libxyz.so` will be converted to `xyz.o` 126 | /// - `xyz.so` will be converted to `xyz.o` 127 | /// - `xyz` will be converted to `xyz.o` 128 | /// 129 | /// Specifically: 130 | /// - If the given soname does not ends with .so (regardless of case), then a plain ".o" suffix will be added to the 131 | /// given name and we're done. 132 | /// - Otherwise, replace the ".so" suffix with ".o". 133 | /// - If the given soname begins with "lib" (regardless of case), remove that prefix. 134 | fn convert_soname_to_object_name(soname: &str) -> String { 135 | let name_core = 'b: { 136 | if soname.len() < 3 { 137 | break 'b soname; 138 | } 139 | 140 | let (file_name_wo_ext, ext_suffix) = soname.split_at(soname.len() - 3); 141 | if ext_suffix.to_lowercase() != ".so" { 142 | // The given soname does not ends with .so. 143 | return format!("{}.o", soname); 144 | } 145 | 146 | if file_name_wo_ext.len() >= 3 { 147 | let (lib_prefix, name_core) = file_name_wo_ext.split_at(3); 148 | if lib_prefix.to_lowercase() == "lib" { 149 | break 'b name_core; 150 | } 151 | } 152 | 153 | file_name_wo_ext 154 | }; 155 | 156 | format!("{}.o", name_core) 157 | } 158 | 159 | fn init_logger(verbosity: u8) -> Result<(), SetLoggerError> { 160 | let level = match verbosity { 161 | 0 => LogLevel::Warn, 162 | 1 => LogLevel::Info, 163 | 2 => LogLevel::Debug, 164 | _ => LogLevel::Trace, 165 | }; 166 | simple_logger::init_with_level(level)?; 167 | Ok(()) 168 | } 169 | 170 | #[derive(Debug)] 171 | struct OutputFile { 172 | path: PathBuf, 173 | file: Option>, 174 | delete_on_drop: bool, 175 | } 176 | 177 | impl OutputFile { 178 | fn create(path: &Path) -> Result { 179 | let file = File::create(path)?; 180 | Ok(Self { 181 | path: PathBuf::from(path), 182 | file: Some(BufWriter::new(file)), 183 | delete_on_drop: true, 184 | }) 185 | } 186 | 187 | fn writer(&mut self) -> &mut BufWriter { 188 | self.file.as_mut().unwrap() 189 | } 190 | 191 | fn prevent_delete_on_drop(&mut self) { 192 | self.delete_on_drop = false; 193 | } 194 | } 195 | 196 | impl Drop for OutputFile { 197 | fn drop(&mut self) { 198 | if !self.delete_on_drop { 199 | return; 200 | } 201 | 202 | self.file.take(); // Close the output file. 203 | std::fs::remove_file(&self.path).ok(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/pass.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::error::Error; 4 | use std::fmt::{Debug, Formatter}; 5 | use std::marker::PhantomData; 6 | 7 | use object::write::Object as OutputObject; 8 | use thiserror::Error; 9 | 10 | /// Represent a pass. 11 | pub trait Pass { 12 | const NAME: &'static str; 13 | 14 | type Output: 'static; 15 | type Error: Error + Send + Sync + 'static; 16 | 17 | /// Run the pass. 18 | fn run(&mut self, ctx: &PassContext) -> Result; 19 | } 20 | 21 | /// Provide context for running a single pass. 22 | pub struct PassContext { 23 | pub input: I, 24 | pub output: RefCell>, 25 | pass_outputs: Vec>, 26 | } 27 | 28 | impl PassContext { 29 | /// Get the value produced by the pass referenced by the given handle. 30 | /// 31 | /// # Panics 32 | /// 33 | /// This function will panic if either: 34 | /// - The given pass handle does not refer to a valid pass in the current context; 35 | /// - The pass referred to by the given pass handle does not have the specified type. 36 | pub fn get_pass_output

(&self, handle: PassHandle

) -> &P::Output 37 | where 38 | P: Pass, 39 | { 40 | self.pass_outputs 41 | .get(handle.idx) 42 | .map(|output| output.downcast_ref().unwrap()) 43 | .unwrap() 44 | } 45 | } 46 | 47 | impl Debug for PassContext 48 | where 49 | I: Debug, 50 | { 51 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 52 | f.debug_struct("PassContext") 53 | .field("input", &self.input) 54 | .field("output", &self.output) 55 | .field( 56 | "pass_outputs", 57 | &format!("[{} values]", self.pass_outputs.len()), 58 | ) 59 | .finish() 60 | } 61 | } 62 | 63 | /// Manage and run a flow of passes. 64 | #[derive(Default)] 65 | pub struct PassManager { 66 | passes: Vec>>, 67 | } 68 | 69 | impl PassManager { 70 | /// Create a new `PassManager` that does not contain any passes. 71 | pub fn new() -> Self { 72 | Self { passes: Vec::new() } 73 | } 74 | 75 | /// Add a pass to the end of the current pass pipeline. 76 | pub fn add_pass

(&mut self, pass: P) -> PassHandle

77 | where 78 | P: Pass + 'static, 79 | { 80 | let idx = self.passes.len(); 81 | self.passes.push(Box::new(pass)); 82 | PassHandle::new(idx) 83 | } 84 | 85 | /// Add a pass to the end of the current pass pipeline. The pass object is created via `Default::default`. 86 | pub fn add_pass_default

(&mut self) -> PassHandle

87 | where 88 | P: Default + Pass + 'static, 89 | { 90 | self.add_pass(P::default()) 91 | } 92 | 93 | /// Run the pass pipeline. 94 | pub fn run( 95 | mut self, 96 | input: I, 97 | output: OutputObject<'static>, 98 | ) -> Result, RunPassError> { 99 | let mut ctx = PassContext { 100 | input, 101 | output: RefCell::new(output), 102 | pass_outputs: Vec::with_capacity(self.passes.len()), 103 | }; 104 | 105 | for current_pass in &mut self.passes { 106 | log::info!("Running pass \"{}\" ...", current_pass.name()); 107 | match current_pass.run(&ctx) { 108 | Ok(result) => { 109 | ctx.pass_outputs.push(result); 110 | } 111 | Err(err) => { 112 | return Err(RunPassError { 113 | name: String::from(current_pass.name()), 114 | error: err, 115 | }); 116 | } 117 | } 118 | } 119 | 120 | Ok(ctx.output.into_inner()) 121 | } 122 | } 123 | 124 | impl Debug for PassManager { 125 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 126 | let pass_names: Vec<_> = self.passes.iter().map(|p| p.name()).collect(); 127 | f.debug_struct("PassManager") 128 | .field("passes", &pass_names) 129 | .finish() 130 | } 131 | } 132 | 133 | /// A lightweight handle to a pass in a [`PassManager`]. 134 | pub struct PassHandle

{ 135 | idx: usize, 136 | _phantom: PhantomData<*const P>, 137 | } 138 | 139 | impl

PassHandle

{ 140 | fn new(idx: usize) -> Self { 141 | Self { 142 | idx, 143 | _phantom: PhantomData::default(), 144 | } 145 | } 146 | } 147 | 148 | impl

Clone for PassHandle

{ 149 | fn clone(&self) -> Self { 150 | Self { 151 | idx: self.idx.clone(), 152 | _phantom: PhantomData::default(), 153 | } 154 | } 155 | } 156 | 157 | impl

Copy for PassHandle

{} 158 | 159 | impl

Debug for PassHandle

{ 160 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 161 | f.debug_struct("PassHandle") 162 | .field("idx", &self.idx) 163 | .finish() 164 | } 165 | } 166 | 167 | /// Errors occured when running a pass pipeline. 168 | #[derive(Debug, Error)] 169 | #[error("pass {name} failed: {error:?}")] 170 | pub struct RunPassError { 171 | /// The name of the specific pass that failed. 172 | pub name: String, 173 | 174 | /// The error value produced by the failed pass. 175 | #[source] 176 | pub error: anyhow::Error, 177 | } 178 | 179 | trait AbstractPass { 180 | fn name(&self) -> &'static str; 181 | fn run(&mut self, ctx: &PassContext) -> anyhow::Result>; 182 | } 183 | 184 | impl AbstractPass for P 185 | where 186 | P: Pass, 187 | { 188 | fn name(&self) -> &'static str { 189 | P::NAME 190 | } 191 | 192 | fn run(&mut self, ctx: &PassContext) -> anyhow::Result> { 193 | let output =

>::run(self, ctx)?; 194 | Ok(Box::new(output)) 195 | } 196 | } 197 | 198 | #[cfg(test)] 199 | pub mod test { 200 | use std::convert::Infallible; 201 | 202 | use super::*; 203 | 204 | pub trait PassTest: 'static { 205 | type Input; 206 | type Pass: Pass; 207 | 208 | fn setup(&mut self, pass_mgr: &mut PassManager) -> PassHandle; 209 | 210 | #[allow(unused_variables)] 211 | fn check_pass_output(&mut self, output: &>::Output) {} 212 | 213 | #[allow(unused_variables)] 214 | fn check_output_object(&mut self, output: &OutputObject<'static>) {} 215 | } 216 | 217 | pub fn run_pass_test(mut test: T, input: T::Input, output: OutputObject<'static>) 218 | where 219 | T: PassTest, 220 | { 221 | let mut pass_mgr = PassManager::new(); 222 | let target_pass = test.setup(&mut pass_mgr); 223 | 224 | pass_mgr.add_pass(TestPass { test, target_pass }); 225 | pass_mgr.run(input, output).unwrap(); 226 | } 227 | 228 | struct TestPass 229 | where 230 | T: PassTest, 231 | { 232 | test: T, 233 | target_pass: PassHandle, 234 | } 235 | 236 | impl Pass for TestPass 237 | where 238 | T: PassTest, 239 | { 240 | const NAME: &'static str = "unit test"; 241 | 242 | type Output = (); 243 | type Error = Infallible; 244 | 245 | fn run(&mut self, ctx: &PassContext) -> Result { 246 | self.test 247 | .check_pass_output(ctx.get_pass_output(self.target_pass)); 248 | self.test.check_output_object(&*ctx.output.borrow()); 249 | Ok(()) 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stringify; 2 | -------------------------------------------------------------------------------- /src/utils/stringify.rs: -------------------------------------------------------------------------------- 1 | use object::{Architecture, BinaryFormat}; 2 | 3 | /// Get the string representation of a `BinaryFormat` value. 4 | pub fn binary_format_to_str(f: BinaryFormat) -> &'static str { 5 | match f { 6 | BinaryFormat::Coff => "coff", 7 | BinaryFormat::Elf => "elf", 8 | BinaryFormat::MachO => "macho", 9 | BinaryFormat::Pe => "pe", 10 | BinaryFormat::Wasm => "wasm", 11 | BinaryFormat::Xcoff => "xcoff", 12 | _ => unreachable!(), 13 | } 14 | } 15 | 16 | /// Get the string representation of an `Architecture` value. 17 | pub fn arch_to_str(arch: Architecture) -> &'static str { 18 | match arch { 19 | Architecture::Unknown => "unknown", 20 | Architecture::Aarch64 => "aarch64", 21 | Architecture::Aarch64_Ilp32 => "aarch64_ilp32", 22 | Architecture::Arm => "arm", 23 | Architecture::Avr => "avr", 24 | Architecture::Bpf => "bpf", 25 | Architecture::Csky => "csky", 26 | Architecture::I386 => "i386", 27 | Architecture::X86_64 => "x86_64", 28 | Architecture::X86_64_X32 => "x32", 29 | Architecture::Hexagon => "hexagon", 30 | Architecture::LoongArch64 => "loongarch64", 31 | Architecture::Mips => "mips", 32 | Architecture::Mips64 => "mips64", 33 | Architecture::Msp430 => "msp430", 34 | Architecture::PowerPc => "powerpc", 35 | Architecture::PowerPc64 => "powerpc64", 36 | Architecture::Riscv32 => "riscv32", 37 | Architecture::Riscv64 => "riscv64", 38 | Architecture::S390x => "s390x", 39 | Architecture::Sbf => "sbf", 40 | Architecture::Sparc64 => "sparc64", 41 | Architecture::Wasm32 => "wasm32", 42 | Architecture::Wasm64 => "wasm64", 43 | Architecture::Xtensa => "xtensa", 44 | _ => unreachable!(), 45 | } 46 | } 47 | --------------------------------------------------------------------------------