gremlin.scm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2018, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix build gremlin)
  19. #:use-module (guix elf)
  20. #:use-module ((guix build utils) #:select (store-file-name?))
  21. #:use-module (ice-9 match)
  22. #:use-module (srfi srfi-1)
  23. #:use-module (srfi srfi-9)
  24. #:use-module (srfi srfi-26)
  25. #:use-module (srfi srfi-34)
  26. #:use-module (srfi srfi-35)
  27. #:use-module (system foreign)
  28. #:use-module (rnrs bytevectors)
  29. #:use-module (rnrs io ports)
  30. #:export (elf-error?
  31. elf-error-elf
  32. invalid-segment-size?
  33. invalid-segment-size-segment
  34. elf-dynamic-info
  35. elf-dynamic-info?
  36. elf-dynamic-info-soname
  37. elf-dynamic-info-needed
  38. elf-dynamic-info-rpath
  39. elf-dynamic-info-runpath
  40. expand-origin
  41. file-dynamic-info
  42. file-runpath
  43. file-needed
  44. file-needed/recursive
  45. missing-runpath-error?
  46. missing-runpath-error-file
  47. runpath-too-long-error?
  48. runpath-too-long-error-file
  49. set-file-runpath
  50. validate-needed-in-runpath
  51. strip-runpath))
  52. ;;; Commentary:
  53. ;;;
  54. ;;; A gremlin is sort-of like an elf, you know, and this module provides tools
  55. ;;; to deal with dynamic-link information from ELF files.
  56. ;;;
  57. ;;; Code:
  58. (define-condition-type &elf-error &error
  59. elf-error?
  60. (elf elf-error-elf))
  61. (define-condition-type &invalid-segment-size &elf-error
  62. invalid-segment-size?
  63. (segment invalid-segment-size-segment))
  64. (define (dynamic-link-segment elf)
  65. "Return the 'PT_DYNAMIC' segment of ELF--i.e., the segment that contains
  66. dynamic linking information."
  67. (let ((size (bytevector-length (elf-bytes elf))))
  68. (find (lambda (segment)
  69. (unless (<= (+ (elf-segment-offset segment)
  70. (elf-segment-filesz segment))
  71. size)
  72. ;; This happens on separate debug output files created by
  73. ;; 'strip --only-keep-debug' (Binutils 2.25.)
  74. (raise (condition (&invalid-segment-size
  75. (elf elf)
  76. (segment segment)))))
  77. (= (elf-segment-type segment) PT_DYNAMIC))
  78. (elf-segments elf))))
  79. (define (word-reader size byte-order)
  80. "Return a procedure to read a word of SIZE bytes according to BYTE-ORDER."
  81. (case size
  82. ((8)
  83. (lambda (bv index)
  84. (bytevector-u64-ref bv index byte-order)))
  85. ((4)
  86. (lambda (bv index)
  87. (bytevector-u32-ref bv index byte-order)))))
  88. ;; Dynamic entry:
  89. ;;
  90. ;; typedef struct
  91. ;; {
  92. ;; Elf64_Sxword d_tag; /* Dynamic entry type */
  93. ;; union
  94. ;; {
  95. ;; Elf64_Xword d_val; /* Integer value */
  96. ;; Elf64_Addr d_ptr; /* Address value */
  97. ;; } d_un;
  98. ;; } Elf64_Dyn;
  99. (define-record-type <dynamic-entry>
  100. (dynamic-entry type value offset)
  101. dynamic-entry?
  102. (type dynamic-entry-type) ;DT_*
  103. (value dynamic-entry-value) ;string | number | ...
  104. (offset dynamic-entry-offset)) ;integer
  105. (define (raw-dynamic-entries elf segment)
  106. "Return as a list of <dynamic-entry> for the dynamic entries found in
  107. SEGMENT, the 'PT_DYNAMIC' segment of ELF."
  108. (define start
  109. (elf-segment-offset segment))
  110. (define bytes
  111. (elf-bytes elf))
  112. (define word-size
  113. (elf-word-size elf))
  114. (define byte-order
  115. (elf-byte-order elf))
  116. (define read-word
  117. (word-reader word-size byte-order))
  118. (let loop ((offset 0)
  119. (result '()))
  120. (if (>= offset (elf-segment-memsz segment))
  121. (reverse result)
  122. (let ((type (read-word bytes (+ start offset)))
  123. (value (read-word bytes (+ start offset word-size))))
  124. (if (= type DT_NULL) ;finished?
  125. (reverse result)
  126. (loop (+ offset (* 2 word-size))
  127. (cons (dynamic-entry type value
  128. (+ start offset word-size))
  129. result)))))))
  130. (define (vma->offset elf vma)
  131. "Convert VMA, a virtual memory address, to an offset within ELF.
  132. Do that by looking at the loadable program segment (PT_LOAD) of ELF that
  133. contains VMA and by taking into account that segment's virtual address and
  134. offset."
  135. ;; See 'offset_from_vma' in Binutils.
  136. (define loads
  137. (filter (lambda (segment)
  138. (= (elf-segment-type segment) PT_LOAD))
  139. (elf-segments elf)))
  140. (let ((load (find (lambda (segment)
  141. (let ((vaddr (elf-segment-vaddr segment)))
  142. (and (>= vma vaddr)
  143. (< vma (+ (elf-segment-memsz segment)
  144. vaddr)))))
  145. loads)))
  146. (+ (- vma (elf-segment-vaddr load))
  147. (elf-segment-offset load))))
  148. (define (dynamic-entries elf segment)
  149. "Return all the dynamic entries found in SEGMENT, the 'PT_DYNAMIC' segment
  150. of ELF, as a list of <dynamic-entry>. The value of each entry may be a string
  151. or an integer depending on the entry type (for instance, the value of
  152. DT_NEEDED entries is a string.) Likewise the offset is the offset within the
  153. string table if the type is a string."
  154. (define entries
  155. (raw-dynamic-entries elf segment))
  156. (define string-table-offset
  157. (any (lambda (entry)
  158. (and (= (dynamic-entry-type entry) DT_STRTAB)
  159. (dynamic-entry-value entry)))
  160. entries))
  161. (define (interpret-dynamic-entry entry)
  162. (let ((type (dynamic-entry-type entry))
  163. (value (dynamic-entry-value entry)))
  164. (cond ((memv type (list DT_NEEDED DT_SONAME DT_RPATH DT_RUNPATH))
  165. (if string-table-offset
  166. (let* ((offset (vma->offset elf (+ string-table-offset value)))
  167. (value (pointer->string
  168. (bytevector->pointer (elf-bytes elf) offset))))
  169. (dynamic-entry type value offset))
  170. (dynamic-entry type value (dynamic-entry-offset entry))))
  171. (else
  172. (dynamic-entry type value (dynamic-entry-offset entry))))))
  173. (map interpret-dynamic-entry entries))
  174. ;;;
  175. ;;; High-level interface.
  176. ;;;
  177. (define-record-type <elf-dynamic-info>
  178. (%elf-dynamic-info soname needed rpath runpath)
  179. elf-dynamic-info?
  180. (soname elf-dynamic-info-soname)
  181. (needed elf-dynamic-info-needed)
  182. (rpath elf-dynamic-info-rpath)
  183. (runpath elf-dynamic-info-runpath))
  184. (define search-path->list
  185. (let ((not-colon (char-set-complement (char-set #\:))))
  186. (lambda (str)
  187. "Split STR on ':' characters."
  188. (string-tokenize str not-colon))))
  189. (define (elf-dynamic-info elf)
  190. "Return dynamic-link information for ELF as an <elf-dynamic-info> object, or
  191. #f if ELF lacks dynamic-link information."
  192. (define (matching-entry type)
  193. (lambda (entry)
  194. (= type (dynamic-entry-type entry))))
  195. (match (dynamic-link-segment elf)
  196. (#f #f)
  197. ((? elf-segment? dynamic)
  198. (let ((entries (dynamic-entries elf dynamic)))
  199. (%elf-dynamic-info (and=> (find (matching-entry DT_SONAME)
  200. entries)
  201. dynamic-entry-value)
  202. (filter-map (lambda (entry)
  203. (and (= (dynamic-entry-type entry)
  204. DT_NEEDED)
  205. (dynamic-entry-value entry)))
  206. entries)
  207. (or (and=> (find (matching-entry DT_RPATH)
  208. entries)
  209. (compose search-path->list
  210. dynamic-entry-value))
  211. '())
  212. (or (and=> (find (matching-entry DT_RUNPATH)
  213. entries)
  214. (compose search-path->list
  215. dynamic-entry-value))
  216. '()))))))
  217. (define (file-dynamic-info file)
  218. "Return the <elf-dynamic-info> record of FILE, or #f if FILE lacks dynamic
  219. info."
  220. (call-with-input-file file
  221. (lambda (port)
  222. (elf-dynamic-info (parse-elf (get-bytevector-all port))))))
  223. (define (file-runpath file)
  224. "Return the DT_RUNPATH dynamic entry of FILE as a list of strings, or #f if
  225. FILE lacks dynamic info."
  226. (and=> (file-dynamic-info file) elf-dynamic-info-runpath))
  227. (define (file-needed file)
  228. "Return the list of DT_NEEDED dynamic entries of FILE, or #f if FILE lacks
  229. dynamic info."
  230. (and=> (file-dynamic-info file) elf-dynamic-info-needed))
  231. (define (file-needed/recursive file)
  232. "Return two values: the list of absolute .so file names FILE depends on,
  233. recursively, and the list of .so file names that could not be found. File
  234. names are resolved by searching the RUNPATH of the file that NEEDs them.
  235. This is similar to the info returned by the 'ldd' command."
  236. (let loop ((files (list file))
  237. (result '())
  238. (not-found '()))
  239. (match files
  240. (()
  241. (values (reverse result)
  242. (reverse (delete-duplicates not-found))))
  243. ((file . rest)
  244. (match (file-dynamic-info file)
  245. (#f
  246. (loop rest result not-found))
  247. (info
  248. (let ((runpath (elf-dynamic-info-runpath info))
  249. (needed (elf-dynamic-info-needed info)))
  250. (if (and runpath needed)
  251. (let* ((runpath (map (cute expand-origin <> (dirname file))
  252. runpath))
  253. (resolved (map (cut search-path runpath <>)
  254. needed))
  255. (failed (filter-map (lambda (needed resolved)
  256. (and (not resolved)
  257. (not (libc-library? needed))
  258. needed))
  259. needed resolved))
  260. (needed (remove (lambda (value)
  261. (or (not value)
  262. ;; XXX: quadratic
  263. (member value result)))
  264. resolved)))
  265. (loop (append rest needed)
  266. (append needed result)
  267. (append failed not-found)))
  268. (loop rest result not-found)))))))))
  269. (define %libc-libraries
  270. ;; List of libraries as of glibc 2.21 (there are more but those are
  271. ;; typically mean to be LD_PRELOADed and thus do not appear as NEEDED.)
  272. '("libanl.so"
  273. "libcrypt.so"
  274. "libc.so"
  275. "libdl.so"
  276. "libm.so"
  277. "libnsl.so" ;NEEDED by nscd
  278. "libpthread.so"
  279. "libresolv.so"
  280. "librt.so"
  281. "libutil.so"))
  282. (define (libc-library? lib)
  283. "Return #t if LIB is one of the libraries shipped with the GNU C Library."
  284. (find (lambda (libc-lib)
  285. (string-prefix? libc-lib lib))
  286. %libc-libraries))
  287. (define (expand-variable str variable value)
  288. "Replace occurrences of '$VARIABLE' or '${VARIABLE}' in STR with VALUE."
  289. (define variables
  290. (list (string-append "$" variable)
  291. (string-append "${" variable "}")))
  292. (let loop ((thing variables)
  293. (str str))
  294. (match thing
  295. (()
  296. str)
  297. ((head tail ...)
  298. (let ((index (string-contains str head))
  299. (len (string-length head)))
  300. (loop (if index variables tail)
  301. (if index
  302. (string-replace str value
  303. index (+ index len))
  304. str)))))))
  305. (define (expand-origin str directory)
  306. "Replace occurrences of '$ORIGIN' in STR with DIRECTORY."
  307. (expand-variable str "ORIGIN" directory))
  308. (define* (validate-needed-in-runpath file
  309. #:key (always-found? libc-library?))
  310. "Return #t if all the libraries listed as FILE's 'DT_NEEDED' entries are
  311. present in its RUNPATH, or if FILE lacks dynamic-link information. Return #f
  312. otherwise. Libraries whose name matches ALWAYS-FOUND? are considered to be
  313. always available."
  314. (guard (c ((invalid-segment-size? c)
  315. (let ((segment (invalid-segment-size-segment c)))
  316. (format (current-error-port)
  317. "~a: error: offset + size of segment ~a (type ~a) \
  318. exceeds total size~%"
  319. file
  320. (elf-segment-index segment)
  321. (elf-segment-type segment))
  322. #f)))
  323. (let* ((elf (call-with-input-file file
  324. (compose parse-elf get-bytevector-all)))
  325. (expand (cute expand-origin <> (dirname file)))
  326. (dyninfo (elf-dynamic-info elf)))
  327. (when dyninfo
  328. ;; XXX: In theory we should also expand $PLATFORM and $LIB, but these
  329. ;; appear to be really unused.
  330. (let* ((expanded (map expand (elf-dynamic-info-runpath dyninfo)))
  331. (runpath (filter store-file-name? expanded))
  332. (bogus (remove store-file-name? expanded))
  333. (needed (remove always-found?
  334. (elf-dynamic-info-needed dyninfo)))
  335. (not-found (remove (cut search-path runpath <>)
  336. needed)))
  337. (unless (null? bogus)
  338. (format (current-error-port)
  339. "~a: warning: RUNPATH contains bogus entries: ~s~%"
  340. file bogus))
  341. (for-each (lambda (lib)
  342. (format (current-error-port)
  343. "~a: error: depends on '~a', which cannot \
  344. be found in RUNPATH ~s~%"
  345. file lib runpath))
  346. not-found)
  347. ;; (when (null? not-found)
  348. ;; (format (current-error-port) "~a is OK~%" file))
  349. (null? not-found))))))
  350. (define (strip-runpath file)
  351. "Remove from the DT_RUNPATH of FILE any entries that are not necessary
  352. according to DT_NEEDED."
  353. (define (minimal-runpath needed runpath)
  354. (filter (lambda (directory)
  355. (and (string-prefix? "/" directory)
  356. (any (lambda (lib)
  357. (file-exists? (string-append directory "/" lib)))
  358. needed)))
  359. runpath))
  360. (define port
  361. (open-file file "r+b"))
  362. (catch #t
  363. (lambda ()
  364. (let* ((elf (parse-elf (get-bytevector-all port)))
  365. (entries (dynamic-entries elf (dynamic-link-segment elf)))
  366. (needed (filter-map (lambda (entry)
  367. (and (= (dynamic-entry-type entry)
  368. DT_NEEDED)
  369. (dynamic-entry-value entry)))
  370. entries))
  371. (runpath (find (lambda (entry)
  372. (= DT_RUNPATH (dynamic-entry-type entry)))
  373. entries))
  374. (old (search-path->list
  375. (dynamic-entry-value runpath)))
  376. (new (minimal-runpath needed old)))
  377. (unless (equal? old new)
  378. (format (current-error-port)
  379. "~a: stripping RUNPATH to ~s (removed ~s)~%"
  380. file new
  381. (lset-difference string=? old new))
  382. (seek port (dynamic-entry-offset runpath) SEEK_SET)
  383. (put-bytevector port (string->utf8 (string-join new ":")))
  384. (put-u8 port 0))
  385. (close-port port)
  386. new))
  387. (lambda (key . args)
  388. (false-if-exception (close-port port))
  389. (apply throw key args))))
  390. (define-condition-type &missing-runpath-error &elf-error
  391. missing-runpath-error?
  392. (file missing-runpath-error-file))
  393. (define-condition-type &runpath-too-long-error &elf-error
  394. runpath-too-long-error?
  395. (file runpath-too-long-error-file))
  396. (define (set-file-runpath file path)
  397. "Set the value of the DT_RUNPATH dynamic entry of FILE, which must name an
  398. ELF file, to PATH, a list of strings. Raise a &missing-runpath-error or
  399. &runpath-too-long-error when appropriate."
  400. (define (call-with-input+output-file file proc)
  401. (let ((port (open-file file "r+b")))
  402. (guard (c (#t (close-port port) (raise c)))
  403. (proc port)
  404. (close-port port))))
  405. (call-with-input+output-file file
  406. (lambda (port)
  407. (let* ((elf (parse-elf (get-bytevector-all port)))
  408. (entries (dynamic-entries elf (dynamic-link-segment elf)))
  409. (runpath (find (lambda (entry)
  410. (= DT_RUNPATH (dynamic-entry-type entry)))
  411. entries))
  412. (path (string->utf8 (string-join path ":"))))
  413. (unless runpath
  414. (raise (condition (&missing-runpath-error (elf elf)
  415. (file file)))))
  416. ;; There might be padding left beyond RUNPATH in the string table, but
  417. ;; we don't know, so assume there's no padding.
  418. (unless (<= (bytevector-length path)
  419. (bytevector-length
  420. (string->utf8 (dynamic-entry-value runpath))))
  421. (raise (condition (&runpath-too-long-error (elf #f #;elf)
  422. (file file)))))
  423. (seek port (dynamic-entry-offset runpath) SEEK_SET)
  424. (put-bytevector port path)
  425. (put-u8 port 0)))))
  426. ;;; Local Variables:
  427. ;;; eval: (put 'call-with-input+output-file 'scheme-indent-function 1)
  428. ;;; End: