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