├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src └── main.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: davidlattimore 2 | 3 | -------------------------------------------------------------------------------- /.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 = "anstream" 7 | version = "0.6.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.4" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.0.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 40 | dependencies = [ 41 | "windows-sys 0.52.0", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys 0.52.0", 52 | ] 53 | 54 | [[package]] 55 | name = "anyhow" 56 | version = "1.0.86" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 59 | 60 | [[package]] 61 | name = "bitflags" 62 | version = "2.5.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 65 | 66 | [[package]] 67 | name = "clap" 68 | version = "4.5.4" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 71 | dependencies = [ 72 | "clap_builder", 73 | "clap_derive", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_builder" 78 | version = "4.5.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 81 | dependencies = [ 82 | "anstream", 83 | "anstyle", 84 | "clap_lex", 85 | "strsim", 86 | "terminal_size", 87 | ] 88 | 89 | [[package]] 90 | name = "clap_derive" 91 | version = "4.5.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 94 | dependencies = [ 95 | "heck", 96 | "proc-macro2", 97 | "quote", 98 | "syn", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_lex" 103 | version = "0.7.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 106 | 107 | [[package]] 108 | name = "colorchoice" 109 | version = "1.0.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 112 | 113 | [[package]] 114 | name = "duplicate-function-checker" 115 | version = "0.1.0" 116 | dependencies = [ 117 | "anyhow", 118 | "clap", 119 | "iced-x86", 120 | "object", 121 | "rustc-demangle", 122 | ] 123 | 124 | [[package]] 125 | name = "errno" 126 | version = "0.3.9" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 129 | dependencies = [ 130 | "libc", 131 | "windows-sys 0.52.0", 132 | ] 133 | 134 | [[package]] 135 | name = "heck" 136 | version = "0.5.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 139 | 140 | [[package]] 141 | name = "iced-x86" 142 | version = "1.21.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" 145 | dependencies = [ 146 | "lazy_static", 147 | ] 148 | 149 | [[package]] 150 | name = "is_terminal_polyfill" 151 | version = "1.70.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 154 | 155 | [[package]] 156 | name = "lazy_static" 157 | version = "1.4.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 160 | 161 | [[package]] 162 | name = "libc" 163 | version = "0.2.155" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 166 | 167 | [[package]] 168 | name = "linux-raw-sys" 169 | version = "0.4.14" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 172 | 173 | [[package]] 174 | name = "memchr" 175 | version = "2.7.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 178 | 179 | [[package]] 180 | name = "object" 181 | version = "0.36.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" 184 | dependencies = [ 185 | "memchr", 186 | ] 187 | 188 | [[package]] 189 | name = "proc-macro2" 190 | version = "1.0.85" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 193 | dependencies = [ 194 | "unicode-ident", 195 | ] 196 | 197 | [[package]] 198 | name = "quote" 199 | version = "1.0.36" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 202 | dependencies = [ 203 | "proc-macro2", 204 | ] 205 | 206 | [[package]] 207 | name = "rustc-demangle" 208 | version = "0.1.24" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 211 | 212 | [[package]] 213 | name = "rustix" 214 | version = "0.38.34" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 217 | dependencies = [ 218 | "bitflags", 219 | "errno", 220 | "libc", 221 | "linux-raw-sys", 222 | "windows-sys 0.52.0", 223 | ] 224 | 225 | [[package]] 226 | name = "strsim" 227 | version = "0.11.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 230 | 231 | [[package]] 232 | name = "syn" 233 | version = "2.0.66" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 236 | dependencies = [ 237 | "proc-macro2", 238 | "quote", 239 | "unicode-ident", 240 | ] 241 | 242 | [[package]] 243 | name = "terminal_size" 244 | version = "0.3.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" 247 | dependencies = [ 248 | "rustix", 249 | "windows-sys 0.48.0", 250 | ] 251 | 252 | [[package]] 253 | name = "unicode-ident" 254 | version = "1.0.12" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 257 | 258 | [[package]] 259 | name = "utf8parse" 260 | version = "0.2.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 263 | 264 | [[package]] 265 | name = "windows-sys" 266 | version = "0.48.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 269 | dependencies = [ 270 | "windows-targets 0.48.5", 271 | ] 272 | 273 | [[package]] 274 | name = "windows-sys" 275 | version = "0.52.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 278 | dependencies = [ 279 | "windows-targets 0.52.5", 280 | ] 281 | 282 | [[package]] 283 | name = "windows-targets" 284 | version = "0.48.5" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 287 | dependencies = [ 288 | "windows_aarch64_gnullvm 0.48.5", 289 | "windows_aarch64_msvc 0.48.5", 290 | "windows_i686_gnu 0.48.5", 291 | "windows_i686_msvc 0.48.5", 292 | "windows_x86_64_gnu 0.48.5", 293 | "windows_x86_64_gnullvm 0.48.5", 294 | "windows_x86_64_msvc 0.48.5", 295 | ] 296 | 297 | [[package]] 298 | name = "windows-targets" 299 | version = "0.52.5" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 302 | dependencies = [ 303 | "windows_aarch64_gnullvm 0.52.5", 304 | "windows_aarch64_msvc 0.52.5", 305 | "windows_i686_gnu 0.52.5", 306 | "windows_i686_gnullvm", 307 | "windows_i686_msvc 0.52.5", 308 | "windows_x86_64_gnu 0.52.5", 309 | "windows_x86_64_gnullvm 0.52.5", 310 | "windows_x86_64_msvc 0.52.5", 311 | ] 312 | 313 | [[package]] 314 | name = "windows_aarch64_gnullvm" 315 | version = "0.48.5" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 318 | 319 | [[package]] 320 | name = "windows_aarch64_gnullvm" 321 | version = "0.52.5" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 324 | 325 | [[package]] 326 | name = "windows_aarch64_msvc" 327 | version = "0.48.5" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 330 | 331 | [[package]] 332 | name = "windows_aarch64_msvc" 333 | version = "0.52.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 336 | 337 | [[package]] 338 | name = "windows_i686_gnu" 339 | version = "0.48.5" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 342 | 343 | [[package]] 344 | name = "windows_i686_gnu" 345 | version = "0.52.5" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 348 | 349 | [[package]] 350 | name = "windows_i686_gnullvm" 351 | version = "0.52.5" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 354 | 355 | [[package]] 356 | name = "windows_i686_msvc" 357 | version = "0.48.5" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 360 | 361 | [[package]] 362 | name = "windows_i686_msvc" 363 | version = "0.52.5" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 366 | 367 | [[package]] 368 | name = "windows_x86_64_gnu" 369 | version = "0.48.5" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 372 | 373 | [[package]] 374 | name = "windows_x86_64_gnu" 375 | version = "0.52.5" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 378 | 379 | [[package]] 380 | name = "windows_x86_64_gnullvm" 381 | version = "0.48.5" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 384 | 385 | [[package]] 386 | name = "windows_x86_64_gnullvm" 387 | version = "0.52.5" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 390 | 391 | [[package]] 392 | name = "windows_x86_64_msvc" 393 | version = "0.48.5" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 396 | 397 | [[package]] 398 | name = "windows_x86_64_msvc" 399 | version = "0.52.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 402 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "duplicate-function-checker" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/davidlattimore/duplicate-function-checker" 6 | description = "Tool to determine how much of your binary is duplicate functions" 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | 10 | [dependencies] 11 | anyhow = "1.0.86" 12 | clap = { version = "4.5.4", features = ["derive", "wrap_help"] } 13 | object = { version = "0.36.0", default-features = false, features = [ 14 | "std", 15 | "read", 16 | "elf", 17 | "pe", 18 | "macho", 19 | ] } 20 | rustc-demangle = "0.1.24" 21 | iced-x86 = { version = "1.21.0", default-features = false, features = [ 22 | "std", 23 | "decoder", 24 | "block_encoder", 25 | ] } 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Duplicate symbol checker 2 | 3 | This tool is intended for determining how much of a compiled Rust binary is composed of identical 4 | functions. 5 | 6 | It relies on debug symbols to find functions, so your binary needs to not have been stripped. 7 | 8 | It works by reading the instructions for each function, normalising them in order to accommodate 9 | differences that are only due to the base address of the function, then grouping by the resulting 10 | instruction bytes. 11 | 12 | It currently only supports x86_64 binaries and has only been tested on Linux. 13 | 14 | Identified duplicate functions have a few different sources: 15 | 16 | - Functions could be identical by chance even though they come from different parts of the codebase. 17 | - Generic functions, once monomorphised, could be identical despite having different generic 18 | arguments. For example, the function might only depend on the size of the generic argument, 19 | meaning that all monomorphisations with types of the same size would end up equal. 20 | - Generic functions might be monomorphised multiple times with the same arguments, but then not get 21 | deduplicated. 22 | 23 | The last of these is what I'm most interested in. Unfortunately it's currently kind of hard to 24 | separate these last two cases. Rustc seems to sometimes emit symbol names that include the generic 25 | arguments, but often it emits symbol names that just have the placeholders, then uses a different 26 | hash at the end of the symbol. 27 | 28 | Recommended usage: 29 | 30 | ```sh 31 | cargo run --release -- --verbose --demangle /path/to/bin 32 | ``` 33 | 34 | ## Sample output 35 | 36 | I'll now show some sample outputs from running the tool on a release build of ripgrep. I don't 37 | include all the output since it's quite long, just a few bits that warrant further discussion. 38 | 39 | ``` 40 | Function size: 103 41 | Copies: 21 42 | Excess bytes: 2060 43 | Names: 44 | 1x `alloc::sync::Arc::drop_slow::hd706a4fa915b4d89` 45 | 1x `alloc::sync::Arc::drop_slow::h87093f1f9dea2d0e` 46 | 1x `alloc::sync::Arc::drop_slow::h10d0dcce72958fd8` 47 | 1x `alloc::sync::Arc::drop_slow::h8c617ac2d907e2aa` 48 | 1x `alloc::sync::Arc::drop_slow::hd71eeda01817a536` 49 | 1x `alloc::sync::Arc::drop_slow::he520a5dd6ca64703` 50 | 1x `alloc::sync::Arc::drop_slow::h628a33a33ecac575` 51 | 1x `alloc::sync::Arc::drop_slow::h8cec9ca0439c3711` 52 | 1x `alloc::sync::Arc::drop_slow::h4cd5ea407012db46` 53 | 1x `alloc::sync::Arc::drop_slow::h224d6f2371018a1c` 54 | 1x `alloc::sync::Arc::drop_slow::h990f0b7fc7e3af11` 55 | 1x `alloc::sync::Arc::drop_slow::h73ba588d5943ac7a` 56 | 1x `alloc::sync::Arc::drop_slow::h2dc0bbd1c9c62e26` 57 | 1x `alloc::sync::Arc::drop_slow::h517054e3fb2dbac5` 58 | 1x `alloc::sync::Arc::drop_slow::hdf2cd5f474fa2393` 59 | 1x `alloc::sync::Arc::drop_slow::h61f7d6c3b84da1e9` 60 | 1x `alloc::sync::Arc::drop_slow::h2487201382634f65` 61 | 1x `alloc::sync::Arc::drop_slow::hfd3d412a64e719d1` 62 | 1x `alloc::sync::Arc::drop_slow::h127b8fb68b2d8622` 63 | 1x `alloc::sync::Arc::drop_slow::h545a994a083aa1dc` 64 | 1x `alloc::sync::Arc::drop_slow::he1370168d3fba403` 65 | ``` 66 | 67 | Here we can see that there's 21 copies of a function for dropping an Arc. We don't know what's in 68 | the Arc. It's likely that each of these functions was created to drop an `Arc` with a different 69 | `T`, but that the machine code ended up identical. 70 | 71 | ``` 72 | Function size: 236 73 | Copies: 26 74 | Excess bytes: 5900 75 | Names: 76 | 3x `core::ptr::drop_in_place::h0ede7a90cb4e4caf` 77 | 3x `core::ptr::drop_in_place::h3dc26697a761e8f9` 78 | 3x `core::ptr::drop_in_place::h3801b4f9aaad7fc2` 79 | 5x `core::ptr::drop_in_place::h9eb8ddad156565f6` 80 | 5x `core::ptr::drop_in_place::hc517e495ab2a88a4` 81 | 4x `core::ptr::drop_in_place::hde4f9e64fcdf6bea` 82 | 3x `core::ptr::drop_in_place::hd22a22559e751911` 83 | ``` 84 | 85 | Here we have 7 different function names, differing only in their hash. The compiler has substituted 86 | the type parameter, so we know that these are all dropping the same type. Each function name then 87 | has several copies. These extra copies of each symbol come from separate codegen units. We can 88 | determine this by rebuilding the binary with `codegen-units=1`, then we get the following: 89 | 90 | ``` 91 | Function size: 236 92 | Copies: 7 93 | Excess bytes: 1416 94 | Names: 95 | 1x `core::ptr::drop_in_place::h92c782dcb35669b7` 96 | 1x `core::ptr::drop_in_place::h67d0912fdfd31563` 97 | 1x `core::ptr::drop_in_place::h11e9189330999400` 98 | 1x `core::ptr::drop_in_place::h3242a4cd1700d56b` 99 | 1x `core::ptr::drop_in_place::h17293025138ff46d` 100 | 1x `core::ptr::drop_in_place::h085ce04cd90a2c3f` 101 | 1x `core::ptr::drop_in_place::h419132e2cb015325` 102 | ``` 103 | 104 | Each of these 7 copies was monomorphised when compiling a different crate. We can verify this by 105 | running the tool without `--demangle` then using grep to locate the .rlib that contains that symbol. 106 | Each of these symbols shows up in a different rlib. 107 | 108 | ## Typical results 109 | 110 | For a release build of ripgrep with rustc 1.78.0, I observe the following: 111 | 112 | | Configuration | % duplicate functions | 113 | | --------------------------------------------------------------------- | --------------------- | 114 | | Default release | 5.8 | 115 | | Release with LTO disabled | 6.4 | 116 | | Release with fat LTO | 2.1 | 117 | | Release with codegen-units=1 | 1.9 | 118 | | Release with codegen-units=1 + fat LTO | 1.8 | 119 | | Release with codegen-units=1 + fat LTO + -Zshare-generics | 0.9 | 120 | | Default debug (-Zshare-generics on by default) | 6.6 | 121 | | Default debug with -Zshare-generics=off | 7.1 | 122 | 123 | If trying to reproduce these numbers, I tested with commit 601e122e9f. 124 | 125 | For a binary with lots more dependencies, I pick on my own crate, the Rust REPL evcxr. In its 126 | release build, 10% of executable bytes are from excess copies of duplicated functions. This drops to 127 | 1% with `codegen-units=1`. 128 | 129 | ## Installation 130 | 131 | Can be installed from crates.io with: 132 | 133 | ```sh 134 | cargo install --locked duplicate-function-checker 135 | ``` 136 | 137 | ## License 138 | 139 | Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) 140 | at your option. 141 | 142 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in 143 | Wild by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 144 | additional terms or conditions. 145 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::bail; 2 | use anyhow::Context; 3 | use clap::Parser as _; 4 | use object::Object as _; 5 | use object::ObjectSection as _; 6 | use object::ObjectSymbol; 7 | use object::SectionKind; 8 | use object::SymbolKind; 9 | use std::borrow::Cow; 10 | use std::collections::HashMap; 11 | use std::hash::Hash; 12 | use std::io::Write as _; 13 | use std::path::Path; 14 | use std::path::PathBuf; 15 | 16 | type Result = core::result::Result; 17 | 18 | /// A tool to determine what percentage of a binary's functions are excess duplicates. A symbol 19 | /// table is needed and functions in the symbol table need to have non-zero sizes. 20 | #[derive(clap::Parser)] 21 | struct Args { 22 | /// Input binary to parse. 23 | bin: PathBuf, 24 | 25 | /// Whether to print information about each duplicate symbol. 26 | #[arg(long)] 27 | verbose: bool, 28 | 29 | /// Whether to demangle symbol names. 30 | #[arg(long)] 31 | demangle: bool, 32 | 33 | /// Whether to demangle symbol names and drop rust's hashes. 34 | #[arg(long)] 35 | demangle_no_hash: bool, 36 | 37 | /// What to key functions by. 38 | #[arg(long, default_value = "instructions")] 39 | key: KeyType, 40 | 41 | /// What to sort results by. 42 | #[arg(long, default_value = "excess-bytes")] 43 | sort: SortType, 44 | } 45 | 46 | #[derive(Clone, Copy, clap::ValueEnum, PartialEq, Eq)] 47 | enum KeyType { 48 | /// Group by normalised instruction bytes. 49 | Instructions, 50 | 51 | /// Key by function name and size. 52 | NameAndSize, 53 | 54 | /// Key by function name and size, but drop the hash added by rustc. This may group 55 | /// monomorphisations that are fundamentally different, so isn't recommended. 56 | NameWithoutRustHash, 57 | } 58 | 59 | #[derive(Clone, Copy, clap::ValueEnum, PartialEq, Eq)] 60 | enum SortType { 61 | /// Sort by excess bytes of function in the binary. 62 | ExcessBytes, 63 | 64 | /// Sort by number of copies. 65 | Copies, 66 | 67 | /// Sort by function size. 68 | Size, 69 | } 70 | 71 | fn main() -> Result { 72 | let args = Args::parse(); 73 | let r = match args.key { 74 | KeyType::NameAndSize => process::(&args.bin, &args), 75 | KeyType::NameWithoutRustHash => process::(&args.bin, &args), 76 | KeyType::Instructions => process::(&args.bin, &args), 77 | }; 78 | r.with_context(|| format!("Failed to process `{}`", args.bin.display()))?; 79 | Ok(()) 80 | } 81 | 82 | trait Key: Hash + Eq + Sized { 83 | fn from_sym<'data>( 84 | sym: &object::Symbol<'data, '_, &'data [u8]>, 85 | inputs: &KeyBuilderInputs, 86 | ) -> Option; 87 | } 88 | 89 | fn process(path: &Path, args: &Args) -> Result { 90 | let data = std::fs::read(path)?; 91 | let object = object::File::parse(data.as_slice())?; 92 | let mut symbols = HashMap::new(); 93 | 94 | let inputs = KeyBuilderInputs::new(&object, args); 95 | let mut considered = 0; 96 | 97 | for sym in object.symbols() { 98 | if sym.kind() != SymbolKind::Text || sym.size() == 0 { 99 | continue; 100 | } 101 | let Some(key) = K::from_sym(&sym, &inputs) else { 102 | continue; 103 | }; 104 | considered += 1; 105 | let info = symbols.entry(key).or_insert_with(|| SymInfo { 106 | count: 0, 107 | names: Default::default(), 108 | function_size: sym.size(), 109 | }); 110 | info.count += 1; 111 | if let Ok(name) = sym.name() { 112 | let key = if args.demangle { 113 | Cow::Owned(rustc_demangle::demangle(name).to_string()) 114 | } else if args.demangle_no_hash { 115 | Cow::Owned(format!("{:#}", rustc_demangle::demangle(name))) 116 | } else { 117 | Cow::Borrowed(name) 118 | }; 119 | *info.names.entry(key).or_default() += 1; 120 | }; 121 | } 122 | 123 | let (duplicated_bytes, duplicated_functions, duplicate_instances) = 124 | symbols.values().fold((0, 0, 0), |prev, v| { 125 | ( 126 | prev.0 + v.excess_bytes(), 127 | prev.1 + if v.count > 1 { 1 } else { 0 }, 128 | prev.2 + v.count.saturating_sub(1), 129 | ) 130 | }); 131 | 132 | let text_size = determine_text_size(&object); 133 | let percent = duplicated_bytes as f64 / text_size as f64; 134 | 135 | if args.verbose { 136 | print_duplicates(symbols, args.sort)?; 137 | } 138 | 139 | if considered == 0 { 140 | if object.symbols().next().is_none() { 141 | bail!("Binary has no symbol table"); 142 | } 143 | bail!("No functions were checked for duplication, symbols may have zero sizes"); 144 | } 145 | 146 | println!( 147 | "Original binary: {} of executable code", 148 | pretty_size(text_size) 149 | ); 150 | println!( 151 | " Excess bytes: {} ({:.1}% of executable code)", 152 | pretty_size(duplicated_bytes), 153 | percent * 100.0 154 | ); 155 | println!( 156 | " Fns: {duplicated_functions} with dupes, {duplicate_instances} excess instances" 157 | ); 158 | 159 | Ok(()) 160 | } 161 | 162 | fn get_fn_bytes<'data>( 163 | sym: &object::Symbol<'data, '_, &'data [u8]>, 164 | object: &object::File<'data, &'data [u8]>, 165 | ) -> Option<&'data [u8]> { 166 | let section = object.section_by_index(sym.section_index()?).ok()?; 167 | let section_data = section.data().ok()?; 168 | let offset = sym.address().checked_sub(section.address())? as usize; 169 | let end = offset + sym.size() as usize; 170 | if end > section_data.len() { 171 | return None; 172 | } 173 | Some(§ion_data[offset..end]) 174 | } 175 | 176 | fn print_duplicates(symbols: HashMap, sort: SortType) -> Result { 177 | let mut symbols = symbols 178 | .into_values() 179 | .filter(|info| info.count > 1) 180 | .collect::>(); 181 | 182 | match sort { 183 | SortType::ExcessBytes => symbols.sort_by_key(|v| v.excess_bytes()), 184 | SortType::Copies => symbols.sort_by_key(|v| v.count), 185 | SortType::Size => symbols.sort_by_key(|v| v.function_size), 186 | }; 187 | 188 | let mut out = std::io::stdout().lock(); 189 | for v in symbols { 190 | writeln!(&mut out, "Function size: {}", pretty_size(v.function_size))?; 191 | writeln!(&mut out, "Copies: {}", v.count)?; 192 | writeln!(&mut out, "Excess bytes: {}", pretty_size(v.excess_bytes()))?; 193 | writeln!(&mut out, "Names:")?; 194 | for (name, count) in &v.names { 195 | writeln!(&mut out, " {count}x `{name}`")?; 196 | } 197 | writeln!(&mut out)?; 198 | } 199 | Ok(()) 200 | } 201 | 202 | fn determine_text_size<'data>(object: &object::File<'data, &'data [u8]>) -> u64 { 203 | object 204 | .sections() 205 | .map(|sec| { 206 | if sec.kind() == SectionKind::Text { 207 | sec.size() 208 | } else { 209 | 0 210 | } 211 | }) 212 | .sum() 213 | } 214 | 215 | #[derive(Clone, PartialEq, Eq, Hash)] 216 | struct NameAndSizeKey { 217 | demangled_name: String, 218 | function_size: u64, 219 | } 220 | 221 | #[derive(Clone, PartialEq, Eq, Hash)] 222 | struct InstructionsKey { 223 | function_bytes: Vec, 224 | } 225 | 226 | struct KeyBuilderInputs<'data, 'inputs> { 227 | max_fn_address: u64, 228 | object: &'inputs object::File<'data, &'data [u8]>, 229 | args: &'inputs Args, 230 | } 231 | impl<'data, 'inputs> KeyBuilderInputs<'data, 'inputs> { 232 | fn new(object: &'inputs object::File<'data, &'data [u8]>, args: &'inputs Args) -> Self { 233 | let max_fn_address = object 234 | .symbols() 235 | .filter(|s| s.kind() == SymbolKind::Text) 236 | .map(|s| s.address()) 237 | .max() 238 | .unwrap_or(0); 239 | Self { 240 | max_fn_address, 241 | object, 242 | args, 243 | } 244 | } 245 | } 246 | 247 | impl Key for NameAndSizeKey { 248 | fn from_sym<'data>( 249 | sym: &object::Symbol<'data, '_, &'data [u8]>, 250 | inputs: &KeyBuilderInputs, 251 | ) -> Option { 252 | let Ok(name) = sym.name() else { 253 | return None; 254 | }; 255 | let Ok(demangled) = rustc_demangle::try_demangle(name) else { 256 | return None; 257 | }; 258 | let demangled_name = if inputs.args.key == KeyType::NameWithoutRustHash { 259 | format!("{demangled:#}") 260 | } else { 261 | demangled.to_string() 262 | }; 263 | Some(NameAndSizeKey { 264 | demangled_name, 265 | function_size: sym.size(), 266 | }) 267 | } 268 | } 269 | 270 | impl Key for InstructionsKey { 271 | fn from_sym<'data>( 272 | sym: &object::Symbol<'data, '_, &'data [u8]>, 273 | inputs: &KeyBuilderInputs, 274 | ) -> Option { 275 | let fn_bytes = get_fn_bytes(sym, inputs.object)?; 276 | // In order to determine if two functions at different addresses are the same, we need to 277 | // fix up IP-relative instructions. We relocate all our functions to the address of the last 278 | // function in the file. If we picked an earlier address, then some relative relocations 279 | // might wrap. If we chose a much later address, then we might exceed a 32 bit offset. 280 | // Although plausibly picking 2**31 would also work OK. 281 | let bytes = normalise_asm(fn_bytes, sym.address(), inputs.max_fn_address).ok()?; 282 | Some(Self { 283 | function_bytes: bytes, 284 | }) 285 | } 286 | } 287 | 288 | struct SymInfo<'data> { 289 | count: u64, 290 | names: HashMap, u32>, 291 | function_size: u64, 292 | } 293 | 294 | impl SymInfo<'_> { 295 | fn excess_bytes(&self) -> u64 { 296 | self.count.saturating_sub(1) * self.function_size 297 | } 298 | } 299 | 300 | fn normalise_asm(fn_bytes: &[u8], base_address: u64, new_address: u64) -> Result> { 301 | const BIT_CLASS: u32 = 64; 302 | let options = iced_x86::DecoderOptions::NONE; 303 | let decoder = iced_x86::Decoder::with_ip(BIT_CLASS, fn_bytes, base_address, options); 304 | let instructions = decoder.into_iter().collect::>(); 305 | let block = iced_x86::InstructionBlock::new(&instructions, new_address); 306 | Ok(iced_x86::BlockEncoder::encode(64, block, iced_x86::BlockEncoderOptions::NONE)?.code_buffer) 307 | } 308 | 309 | fn pretty_size(size: u64) -> String { 310 | const KIBIBYTE: u64 = 1024; 311 | const MEBIBYTE: u64 = 1_048_576; 312 | const GIBIBYTE: u64 = 1_073_741_824; 313 | const TEBIBYTE: u64 = 1_099_511_627_776; 314 | const PEBIBYTE: u64 = 1_125_899_906_842_624; 315 | const EXBIBYTE: u64 = 1_152_921_504_606_846_976; 316 | 317 | let (size, symbol) = match size { 318 | size if size < KIBIBYTE => (size as f64, "B"), 319 | size if size < MEBIBYTE => (size as f64 / KIBIBYTE as f64, "KiB"), 320 | size if size < GIBIBYTE => (size as f64 / MEBIBYTE as f64, "MiB"), 321 | size if size < TEBIBYTE => (size as f64 / GIBIBYTE as f64, "GiB"), 322 | size if size < PEBIBYTE => (size as f64 / TEBIBYTE as f64, "TiB"), 323 | size if size < EXBIBYTE => (size as f64 / PEBIBYTE as f64, "PiB"), 324 | _ => (size as f64 / EXBIBYTE as f64, "EiB"), 325 | }; 326 | 327 | format!("{:.1}{}", size, symbol) 328 | } 329 | --------------------------------------------------------------------------------