generate_cfi_kallsyms.pl 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. #!/usr/bin/env perl
  2. # SPDX-License-Identifier: GPL-2.0
  3. #
  4. # Generates a list of Control-Flow Integrity (CFI) jump table symbols
  5. # for kallsyms.
  6. #
  7. # Copyright (C) 2021 Google LLC
  8. use strict;
  9. use warnings;
  10. ## parameters
  11. my $ismodule = 0;
  12. my $file;
  13. foreach (@ARGV) {
  14. if ($_ eq '--module') {
  15. $ismodule = 1;
  16. } elsif (!defined($file)) {
  17. $file = $_;
  18. } else {
  19. die "$0: usage $0 [--module] binary";
  20. }
  21. }
  22. ## environment
  23. my $readelf = $ENV{'READELF'} || die "$0: ERROR: READELF not set?";
  24. my $objdump = $ENV{'OBJDUMP'} || die "$0: ERROR: OBJDUMP not set?";
  25. my $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
  26. ## jump table addresses
  27. my $cfi_jt = {};
  28. ## text symbols
  29. my $text_symbols = {};
  30. ## parser state
  31. use constant {
  32. UNKNOWN => 0,
  33. SYMBOL => 1,
  34. HINT => 2,
  35. BRANCH => 3,
  36. RELOC => 4
  37. };
  38. ## trims leading zeros from a string
  39. sub trim_zeros {
  40. my ($n) = @_;
  41. $n =~ s/^0+//;
  42. $n = 0 if ($n eq '');
  43. return $n;
  44. }
  45. ## finds __cfi_jt_* symbols from the binary to locate the start and end of the
  46. ## jump table
  47. sub find_cfi_jt {
  48. open(my $fh, "\"$readelf\" --symbols \"$file\" 2>/dev/null | grep __cfi_jt_ |")
  49. or die "$0: ERROR: failed to execute \"$readelf\": $!";
  50. while (<$fh>) {
  51. chomp;
  52. my ($addr, $name) = $_ =~ /\:.*([a-f0-9]{16}).*\s__cfi_jt_(.*)/;
  53. if (defined($addr) && defined($name)) {
  54. $cfi_jt->{$name} = $addr;
  55. }
  56. }
  57. close($fh);
  58. die "$0: ERROR: __cfi_jt_start symbol missing" if !exists($cfi_jt->{"start"});
  59. die "$0: ERROR: __cfi_jt_end symbol missing" if !exists($cfi_jt->{"end"});
  60. }
  61. my $last = UNKNOWN;
  62. my $last_symbol;
  63. my $last_hint_addr;
  64. my $last_branch_addr;
  65. my $last_branch_target;
  66. my $last_reloc_target;
  67. sub is_symbol {
  68. my ($line) = @_;
  69. my ($addr, $symbol) = $_ =~ /^([a-f0-9]{16})\s<([^>]+)>\:/;
  70. if (defined($addr) && defined($symbol)) {
  71. $last = SYMBOL;
  72. $last_symbol = $symbol;
  73. return 1;
  74. }
  75. return 0;
  76. }
  77. sub is_hint {
  78. my ($line) = @_;
  79. my ($hint) = $_ =~ /^\s*([a-f0-9]+)\:.*\s+hint\s+#/;
  80. if (defined($hint)) {
  81. $last = HINT;
  82. $last_hint_addr = $hint;
  83. return 1;
  84. }
  85. return 0;
  86. }
  87. sub find_text_symbol {
  88. my ($target) = @_;
  89. my ($symbol, $expr, $offset) = $target =~ /^(\S*)([-\+])0x([a-f0-9]+)?$/;
  90. if (!defined($symbol) || !defined(!$expr) || !defined($offset)) {
  91. return $target;
  92. }
  93. if ($symbol =~ /^\.((init|exit)\.)?text$/ && $expr eq '+') {
  94. $offset = trim_zeros($offset);
  95. my $actual = $text_symbols->{"$symbol+$offset"};
  96. if (!defined($actual)) {
  97. die "$0: unknown symbol at $symbol+0x$offset";
  98. }
  99. $symbol = $actual;
  100. }
  101. return $symbol;
  102. }
  103. sub is_branch {
  104. my ($line) = @_;
  105. my ($addr, $instr, $branch_target) = $_ =~
  106. /^\s*([a-f0-9]+)\:.*(b|jmpq?)\s+0x[a-f0-9]+\s+<([^>]+)>/;
  107. if (defined($addr) && defined($instr) && defined($branch_target)) {
  108. if ($last eq HINT) {
  109. $last_branch_addr = $last_hint_addr;
  110. } else {
  111. $last_branch_addr = $addr;
  112. }
  113. $last = BRANCH;
  114. $last_branch_target = find_text_symbol($branch_target);
  115. return 1;
  116. }
  117. return 0;
  118. }
  119. sub is_branch_reloc {
  120. my ($line) = @_;
  121. if ($last ne BRANCH) {
  122. return 0;
  123. }
  124. my ($addr, $type, $reloc_target) = /\s*([a-f0-9]{16})\:\s+R_(\S+)\s+(\S+)$/;
  125. if (defined($addr) && defined($type) && defined($reloc_target)) {
  126. $last = RELOC;
  127. $last_reloc_target = find_text_symbol($reloc_target);
  128. return 1;
  129. }
  130. return 0;
  131. }
  132. ## walks through the jump table looking for branches and prints out a jump
  133. ## table symbol for each branch if one is missing
  134. sub print_missing_symbols {
  135. my @symbols;
  136. open(my $fh, "\"$objdump\" -d -r " .
  137. "--start-address=0x" . $cfi_jt->{"start"} .
  138. " --stop-address=0x" . $cfi_jt->{"end"} .
  139. " \"$file\" 2>/dev/null |")
  140. or die "$0: ERROR: failed to execute \"$objdump\": $!";
  141. while (<$fh>) {
  142. chomp;
  143. if (is_symbol($_) || is_hint($_)) {
  144. next;
  145. }
  146. my $cfi_jt_symbol;
  147. if (is_branch($_)) {
  148. if ($ismodule) {
  149. next; # wait for the relocation
  150. }
  151. $cfi_jt_symbol = $last_branch_target;
  152. } elsif (is_branch_reloc($_)) {
  153. $cfi_jt_symbol = $last_reloc_target;
  154. } else {
  155. next;
  156. }
  157. # ignore functions with a canonical jump table
  158. if ($cfi_jt_symbol =~ /\.cfi$/) {
  159. next;
  160. }
  161. $cfi_jt_symbol .= ".cfi_jt";
  162. $cfi_jt->{$last_branch_addr} = $cfi_jt_symbol;
  163. if (defined($last_symbol) && $last_symbol eq $cfi_jt_symbol) {
  164. next; # already exists
  165. }
  166. # print out the symbol
  167. if ($ismodule) {
  168. push(@symbols, "\t\t$cfi_jt_symbol = . + 0x$last_branch_addr;");
  169. } else {
  170. push(@symbols, "$last_branch_addr t $cfi_jt_symbol");
  171. }
  172. }
  173. close($fh);
  174. if (!scalar(@symbols)) {
  175. return;
  176. }
  177. if ($ismodule) {
  178. print "SECTIONS {\n";
  179. # With -fpatchable-function-entry, LLD isn't happy without this
  180. print "\t__patchable_function_entries : { *(__patchable_function_entries) }\n";
  181. print "\t.text : {\n";
  182. }
  183. foreach (@symbols) {
  184. print "$_\n";
  185. }
  186. if ($ismodule) {
  187. print "\t}\n}\n";
  188. }
  189. }
  190. ## reads defined text symbols from the file
  191. sub read_symbols {
  192. open(my $fh, "\"$objdump\" --syms \"$file\" 2>/dev/null |")
  193. or die "$0: ERROR: failed to execute \"$nm\": $!";
  194. while (<$fh>) {
  195. chomp;
  196. # llvm/tools/llvm-objdump/objdump.cpp:objdump::printSymbol
  197. my ($addr, $debug, $section, $ref, $symbol) = $_ =~
  198. /^([a-f0-9]{16})\s.{5}(.).{2}(\S+)\s[a-f0-9]{16}(\s\.\S+)?\s(.*)$/;
  199. if (defined($addr) && defined($section) && defined($symbol)) {
  200. if (!($section =~ /^\.((init|exit)\.)?text$/)) {
  201. next;
  202. }
  203. # skip arm mapping symbols
  204. if ($symbol =~ /^\$[xd]\.\d+$/) {
  205. next;
  206. }
  207. if (defined($debug) && $debug eq "d") {
  208. next;
  209. }
  210. $addr = trim_zeros($addr);
  211. $text_symbols->{"$section+$addr"} = $symbol;
  212. }
  213. }
  214. close($fh);
  215. }
  216. ## prints out the remaining symbols from nm -n, filtering out the unnecessary
  217. ## __typeid__ symbols aliasing the jump table symbols we added
  218. sub print_kallsyms {
  219. open(my $fh, "\"$nm\" -n \"$file\" 2>/dev/null |")
  220. or die "$0: ERROR: failed to execute \"$nm\": $!";
  221. while (<$fh>) {
  222. chomp;
  223. my ($addr, $symbol) = $_ =~ /^([a-f0-9]{16})\s.\s(.*)$/;
  224. if (defined($addr) && defined($symbol)) {
  225. # drop duplicate __typeid__ symbols
  226. if ($symbol =~ /^__typeid__.*_global_addr$/ &&
  227. exists($cfi_jt->{$addr})) {
  228. next;
  229. }
  230. }
  231. print "$_\n";
  232. }
  233. close($fh);
  234. }
  235. ## main
  236. find_cfi_jt();
  237. if ($ismodule) {
  238. read_symbols();
  239. print_missing_symbols();
  240. } else {
  241. print_missing_symbols();
  242. print_kallsyms();
  243. }