1#!/usr/bin/env bash 2 3# Copyright 2018 The Fuchsia Authors 4# 5# Use of this source code is governed by a MIT-style 6# license that can be found in the LICENSE file or at 7# https://opensource.org/licenses/MIT 8 9# 10# This script examines the relocations left in the kernel ELF image 11# by the linker's --emit-relocs feature to find the boot-time data 12# fixups that need to be done for KASLR. The output is a series of 13# assembly lines that look like this: 14# fixup 0xADDR, COUNT, STRIDE 15# This says that at virtual address 0xADDR there is a naturally aligned 16# address (64-bit) word that needs to be adjusted. This starts a run of 17# COUNT addresses separated by STRIDE bytes (so STRIDE=8 if contiguous). 18# Both 0xADDR and the address value it points to are "link-time absolute", 19# meaning the first byte of the kernel image has the address that the ELF 20# symbol __code_start says. Each address word is incremented by the 21# difference between the chosen run-time virtual address of the kernel 22# and the link-time __code_start value. 23# 24# kernel/arch/CPU/image.S implements the `fixup` assembly macro to apply 25# each run of adjustments, and then #include's the output of this script. 26 27usage() { 28 echo >&2 "Usage: $0 [--pure] KERNEL READELF OBJDUMP OUTFILE" 29 exit 2 30} 31 32PURE=0 33if [ $# -eq 5 -a "$1" = --pure ]; then 34 PURE=1 35 shift 36fi 37 38if [ $# -ne 4 ]; then 39 usage 40fi 41 42AWK=awk 43KERNEL="$1" 44READELF="$2" 45OBJDUMP="$3" 46OUTFILE="$4" 47 48grok_fixups() { 49 "$AWK" -v kernel="$KERNEL" -v objdump="$OBJDUMP" -v pure=$PURE ' 50BEGIN { 51 nrelocs = 0; 52 status = 0; 53 address_prefix = ""; 54 fixup_types["R_X86_64_64"] = 1; 55 fixup_types["R_AARCH64_ABS64"] = 1; 56} 57# In GNU awk, this is just: return strtonum("0x" string) 58# But for least-common-denominator awk, you really have to do it by hand. 59function hex2num(string) { 60 hexdigits["0"] = 0; 61 hexdigits["1"] = 1; 62 hexdigits["2"] = 2; 63 hexdigits["3"] = 3; 64 hexdigits["4"] = 4; 65 hexdigits["5"] = 5; 66 hexdigits["6"] = 6; 67 hexdigits["7"] = 7; 68 hexdigits["8"] = 8; 69 hexdigits["9"] = 9; 70 hexdigits["a"] = 10; 71 hexdigits["b"] = 11; 72 hexdigits["c"] = 12; 73 hexdigits["d"] = 13; 74 hexdigits["e"] = 14; 75 hexdigits["f"] = 15; 76 hexval = 0; 77 while (string != "") { 78 hexval = (hexval * 16) + hexdigits[substr(string, 1, 1)]; 79 string = substr(string, 2, length(string) - 1); 80 } 81 return hexval; 82} 83$1 == "Relocation" && $2 == "section" { 84 secname = $3; 85 sub(/'\''$/, "", secname); 86 sub(/^'\''\.rela/, "", secname); 87 next 88} 89NF == 0 || $1 == "Offset" { next } 90# Ignore standard non-allocated sections. 91secname ~ /^\.debug/ || secname == ".comment" { next } 92# .text.boot contains code that runs before fixups. 93secname == ".text.boot" { next } 94$3 == "R_X86_64_PC32" || $3 == "R_X86_64_PLT32" || \ 95$3 == "R_AARCH64_PREL32" || $3 == "R_AARCH64_PREL64" || \ 96$3 == "R_AARCH64_CALL26" || $3 == "R_AARCH64_JUMP26" || \ 97$3 == "R_AARCH64_CONDBR19" || $3 == "R_AARCH64_TSTBR14" || \ 98$3 ~ /^R_AARCH64_ADR_/ || $3 ~ /^R_AARCH64_.*ABS_L/ { 99 # PC-relative relocs need no fixup. 100 next 101} 102{ 103 # awk handles large integers poorly, so factor out the high 40 bits. 104 this_prefix = substr($1, 1, 10) 105 raw_offset = substr($1, 10) 106 if (address_prefix == "") { 107 address_prefix = this_prefix; 108 } else if (this_prefix != address_prefix) { 109 print "offset", $1, "prefix", this_prefix, "!=", address_prefix > "/dev/stderr"; 110 status = 1; 111 next; 112 } 113 r_offset = hex2num(raw_offset); 114 type = $3; 115 if (!(type in fixup_types)) { 116 bad = "reloc type " + type 117 } else if (secname == ".text.bootstrap16") { 118 # This section is a special case with some movabs instructions 119 # that can be fixed up safely but their immediates are not aligned. 120 bad = 0; 121 } else if (r_offset % 8 != 0) { 122 bad = "misaligned r_offset"; 123 } else if (secname !~ /^\.(ro)?data|^\.kcounter.desc|\.init_array|code_patch_table/) { 124 bad = "fixup in unexpected section" 125 } else { 126 bad = 0; 127 } 128 if (!bad) { 129 relocs[++nrelocs] = r_offset; 130 reloc_secname[r_offset] = secname; 131 } else { 132 print "cannot handle", bad, "at", $1, "in", secname > "/dev/stderr"; 133 status = 1; 134 objdump_cmd = sprintf("\ 135\"%s\" -rdlC --start-address=0x%s%.*x --stop-address=0x%s%.*x %s", objdump, 136 this_prefix, 16 - length(this_prefix), r_offset - 8, 137 this_prefix, 16 - length(this_prefix), r_offset + 8, 138 kernel); 139 sed_cmd = sprintf("\ 140sed '\''1,/^Disassembly/d;/^$/d;s/^/ /;/%s/s/^ /=>/'\''", $1); 141 system(objdump_cmd " | " sed_cmd " >&2"); 142 } 143} 144END { 145 # This is just asort(relocs) in GNU awk, but mawk has no such function. 146 # Bubble sort ftw. 147 for (n = 1; n < nrelocs; ++n) { 148 for (i = 1; i <= nrelocs - 1; ++i) { 149 if (relocs[i] > relocs[i + 1]) { 150 tmp = relocs[i]; 151 relocs[i] = relocs[i + 1]; 152 relocs[i + 1] = tmp; 153 } 154 } 155 } 156 157 if (pure) { 158 if (nrelocs > 0) { 159 print "Binary not purely position-independent: needs", nrelocs, "fixups" > "/dev/stderr"; 160 for (i = 1; i <= nrelocs; ++i) { 161 printf " 0x%s%.*x\n", address_prefix, 16 - length(address_prefix), reloc[i] > "/dev/stderr"; 162 } 163 exit(1); 164 } 165 } else if (nrelocs == 0) { 166 print "Kernel should have some fixups!" > "/dev/stderr"; 167 exit(1); 168 } 169 170 # 256 bytes is the max reach of a load/store post indexed instruction on arm64 171 max_stride = 256; 172 173 run_start = -1; 174 run_length = 0; 175 run_stride = 0; 176 for (i = 1; i <= nrelocs; ++i) { 177 offset = relocs[i]; 178 if (offset == run_start + (run_length * run_stride)) { 179 ++run_length; 180 } else if (i > 0 && run_length == 1 && 181 (offset - run_start) % 8 == 0 && 182 (offset - run_start) < max_stride) { 183 run_stride = offset - run_start; 184 run_length = 2; 185 } else { 186 if (run_length > 0) { 187 printf "fixup 0x%s%.*x, %u, %u // %s\n", address_prefix, 16 - length(address_prefix), run_start, run_length, run_stride, reloc_secname[run_start]; 188 } 189 run_start = offset; 190 run_length = 1; 191 run_stride = 8; 192 } 193 } 194 printf "fixup 0x%s%.*x, %u, %u // %s\n", address_prefix, 16 - length(address_prefix), run_start, run_length, run_stride, reloc_secname[run_start]; 195 exit(status); 196}' 197} 198 199set -e 200if [ -n "$BASH_VERSION" ]; then 201 set -o pipefail 202fi 203 204trap 'rm -f "$OUTFILE"' ERR 205 206LC_ALL=C "$READELF" -W -r "$KERNEL" | grok_fixups > "$OUTFILE" 207