gremlin.scm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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 (find (matching-entry DT_SONAME) entries)
  200. (filter-map (lambda (entry)
  201. (and (= (dynamic-entry-type entry)
  202. DT_NEEDED)
  203. (dynamic-entry-value entry)))
  204. entries)
  205. (or (and=> (find (matching-entry DT_RPATH)
  206. entries)
  207. (compose search-path->list
  208. dynamic-entry-value))
  209. '())
  210. (or (and=> (find (matching-entry DT_RUNPATH)
  211. entries)
  212. (compose search-path->list
  213. dynamic-entry-value))
  214. '()))))))
  215. (define (file-dynamic-info file)
  216. "Return the <elf-dynamic-info> record of FILE, or #f if FILE lacks dynamic
  217. info."
  218. (call-with-input-file file
  219. (lambda (port)
  220. (elf-dynamic-info (parse-elf (get-bytevector-all port))))))
  221. (define (file-runpath file)
  222. "Return the DT_RUNPATH dynamic entry of FILE as a list of strings, or #f if
  223. FILE lacks dynamic info."
  224. (and=> (file-dynamic-info file) elf-dynamic-info-runpath))
  225. (define (file-needed file)
  226. "Return the list of DT_NEEDED dynamic entries of FILE, or #f if FILE lacks
  227. dynamic info."
  228. (and=> (file-dynamic-info file) elf-dynamic-info-needed))
  229. (define (file-needed/recursive file)
  230. "Return two values: the list of absolute .so file names FILE depends on,
  231. recursively, and the list of .so file names that could not be found. File
  232. names are resolved by searching the RUNPATH of the file that NEEDs them.
  233. This is similar to the info returned by the 'ldd' command."
  234. (let loop ((files (list file))
  235. (result '())
  236. (not-found '()))
  237. (match files
  238. (()
  239. (values (reverse result)
  240. (reverse (delete-duplicates not-found))))
  241. ((file . rest)
  242. (match (file-dynamic-info file)
  243. (#f
  244. (loop rest result not-found))
  245. (info
  246. (let ((runpath (elf-dynamic-info-runpath info))
  247. (needed (elf-dynamic-info-needed info)))
  248. (if (and runpath needed)
  249. (let* ((runpath (map (cute expand-origin <> (dirname file))
  250. runpath))
  251. (resolved (map (cut search-path runpath <>)
  252. needed))
  253. (failed (filter-map (lambda (needed resolved)
  254. (and (not resolved)
  255. (not (libc-library? needed))
  256. needed))
  257. needed resolved))
  258. (needed (remove (lambda (value)
  259. (or (not value)
  260. ;; XXX: quadratic
  261. (member value result)))
  262. resolved)))
  263. (loop (append rest needed)
  264. (append needed result)
  265. (append failed not-found)))
  266. (loop rest result not-found)))))))))
  267. (define %libc-libraries
  268. ;; List of libraries as of glibc 2.21 (there are more but those are
  269. ;; typically mean to be LD_PRELOADed and thus do not appear as NEEDED.)
  270. '("libanl.so"
  271. "libcrypt.so"
  272. "libc.so"
  273. "libdl.so"
  274. "libm.so"
  275. "libnsl.so" ;NEEDED by nscd
  276. "libpthread.so"
  277. "libresolv.so"
  278. "librt.so"
  279. "libutil.so"))
  280. (define (libc-library? lib)
  281. "Return #t if LIB is one of the libraries shipped with the GNU C Library."
  282. (find (lambda (libc-lib)
  283. (string-prefix? libc-lib lib))
  284. %libc-libraries))
  285. (define (expand-variable str variable value)
  286. "Replace occurrences of '$VARIABLE' or '${VARIABLE}' in STR with VALUE."
  287. (define variables
  288. (list (string-append "$" variable)
  289. (string-append "${" variable "}")))
  290. (let loop ((thing variables)
  291. (str str))
  292. (match thing
  293. (()
  294. str)
  295. ((head tail ...)
  296. (let ((index (string-contains str head))
  297. (len (string-length head)))
  298. (loop (if index variables tail)
  299. (if index
  300. (string-replace str value
  301. index (+ index len))
  302. str)))))))
  303. (define (expand-origin str directory)
  304. "Replace occurrences of '$ORIGIN' in STR with DIRECTORY."
  305. (expand-variable str "ORIGIN" directory))
  306. (define* (validate-needed-in-runpath file
  307. #:key (always-found? libc-library?))
  308. "Return #t if all the libraries listed as FILE's 'DT_NEEDED' entries are
  309. present in its RUNPATH, or if FILE lacks dynamic-link information. Return #f
  310. otherwise. Libraries whose name matches ALWAYS-FOUND? are considered to be
  311. always available."
  312. (guard (c ((invalid-segment-size? c)
  313. (let ((segment (invalid-segment-size-segment c)))
  314. (format (current-error-port)
  315. "~a: error: offset + size of segment ~a (type ~a) \
  316. exceeds total size~%"
  317. file
  318. (elf-segment-index segment)
  319. (elf-segment-type segment))
  320. #f)))
  321. (let* ((elf (call-with-input-file file
  322. (compose parse-elf get-bytevector-all)))
  323. (expand (cute expand-origin <> (dirname file)))
  324. (dyninfo (elf-dynamic-info elf)))
  325. (when dyninfo
  326. ;; XXX: In theory we should also expand $PLATFORM and $LIB, but these
  327. ;; appear to be really unused.
  328. (let* ((expanded (map expand (elf-dynamic-info-runpath dyninfo)))
  329. (runpath (filter store-file-name? expanded))
  330. (bogus (remove store-file-name? expanded))
  331. (needed (remove always-found?
  332. (elf-dynamic-info-needed dyninfo)))
  333. (not-found (remove (cut search-path runpath <>)
  334. needed)))
  335. (unless (null? bogus)
  336. (format (current-error-port)
  337. "~a: warning: RUNPATH contains bogus entries: ~s~%"
  338. file bogus))
  339. (for-each (lambda (lib)
  340. (format (current-error-port)
  341. "~a: error: depends on '~a', which cannot \
  342. be found in RUNPATH ~s~%"
  343. file lib runpath))
  344. not-found)
  345. ;; (when (null? not-found)
  346. ;; (format (current-error-port) "~a is OK~%" file))
  347. (null? not-found))))))
  348. (define (strip-runpath file)
  349. "Remove from the DT_RUNPATH of FILE any entries that are not necessary
  350. according to DT_NEEDED."
  351. (define (minimal-runpath needed runpath)
  352. (filter (lambda (directory)
  353. (and (string-prefix? "/" directory)
  354. (any (lambda (lib)
  355. (file-exists? (string-append directory "/" lib)))
  356. needed)))
  357. runpath))
  358. (define port
  359. (open-file file "r+b"))
  360. (catch #t
  361. (lambda ()
  362. (let* ((elf (parse-elf (get-bytevector-all port)))
  363. (entries (dynamic-entries elf (dynamic-link-segment elf)))
  364. (needed (filter-map (lambda (entry)
  365. (and (= (dynamic-entry-type entry)
  366. DT_NEEDED)
  367. (dynamic-entry-value entry)))
  368. entries))
  369. (runpath (find (lambda (entry)
  370. (= DT_RUNPATH (dynamic-entry-type entry)))
  371. entries))
  372. (old (search-path->list
  373. (dynamic-entry-value runpath)))
  374. (new (minimal-runpath needed old)))
  375. (unless (equal? old new)
  376. (format (current-error-port)
  377. "~a: stripping RUNPATH to ~s (removed ~s)~%"
  378. file new
  379. (lset-difference string=? old new))
  380. (seek port (dynamic-entry-offset runpath) SEEK_SET)
  381. (put-bytevector port (string->utf8 (string-join new ":")))
  382. (put-u8 port 0))
  383. (close-port port)
  384. new))
  385. (lambda (key . args)
  386. (false-if-exception (close-port port))
  387. (apply throw key args))))
  388. (define-condition-type &missing-runpath-error &elf-error
  389. missing-runpath-error?
  390. (file missing-runpath-error-file))
  391. (define-condition-type &runpath-too-long-error &elf-error
  392. runpath-too-long-error?
  393. (file runpath-too-long-error-file))
  394. (define (set-file-runpath file path)
  395. "Set the value of the DT_RUNPATH dynamic entry of FILE, which must name an
  396. ELF file, to PATH, a list of strings. Raise a &missing-runpath-error or
  397. &runpath-too-long-error when appropriate."
  398. (define (call-with-input+output-file file proc)
  399. (let ((port (open-file file "r+b")))
  400. (guard (c (#t (close-port port) (raise c)))
  401. (proc port)
  402. (close-port port))))
  403. (call-with-input+output-file file
  404. (lambda (port)
  405. (let* ((elf (parse-elf (get-bytevector-all port)))
  406. (entries (dynamic-entries elf (dynamic-link-segment elf)))
  407. (runpath (find (lambda (entry)
  408. (= DT_RUNPATH (dynamic-entry-type entry)))
  409. entries))
  410. (path (string->utf8 (string-join path ":"))))
  411. (unless runpath
  412. (raise (condition (&missing-runpath-error (elf elf)
  413. (file file)))))
  414. ;; There might be padding left beyond RUNPATH in the string table, but
  415. ;; we don't know, so assume there's no padding.
  416. (unless (<= (bytevector-length path)
  417. (bytevector-length
  418. (string->utf8 (dynamic-entry-value runpath))))
  419. (raise (condition (&runpath-too-long-error (elf #f #;elf)
  420. (file file)))))
  421. (seek port (dynamic-entry-offset runpath) SEEK_SET)
  422. (put-bytevector port path)
  423. (put-u8 port 0)))))
  424. ;;; Local Variables:
  425. ;;; eval: (put 'call-with-input+output-file 'scheme-indent-function 1)
  426. ;;; End: