mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
x86/boot: Rework how 32bit C is linked/included for early boot
Right now, the two functions which were really too complicated to write in asm are compiled as 32bit PIC, linked to a blob and included directly, using global asm() to arrange for them to have function semantics. This is limiting and fragile; the use of data relocations will compile fine but malfunction when used, creating hard-to-debug bugs. Furthermore, we would like to increase the amount of C, to deduplicate/unify Xen's boot logic, as well as making it easier to follow. Therefore, rework how the 32bit objects are included. Link all 32bit objects together first. This allows for sharing of logic between translation units. Use differential linking and explicit imports/exports to confirm that we only have the expected relocations, and write the object back out as an assembly file so it can be linked again as if it were 64bit, to integrate with the rest of Xen. This allows for the use of external references (e.g. access to global variables) with reasonable assurance of doing so safely. No functional change. Signed-off-by: Frediano Ziglio <frediano.ziglio@cloud.com> Reviewed-by: Anthony PERARD <anthony.perard@vates.tech> Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
This commit is contained in:
1 parent
fa1463cecf
commit
aa9045e771
7 files changed
+314
-51
No files matched your search
@@ -1,3 +1,5 @@
|
||||
/mkelf32
|
||||
/*.bin
|
||||
/*.lnk
|
||||
/build32.*.lds
|
||||
/built-in-32.*.bin
|
||||
/built-in-32.*.map
|
||||
/built-in-32.S
|
||||
@@ -1,4 +1,5 @@
|
||||
obj-bin-y += head.o
|
||||
obj-bin-y += built-in-32.o
|
||||
|
||||
obj32 := cmdline.32.o
|
||||
obj32 += reloc.32.o
|
||||
@@ -9,9 +10,6 @@ targets += $(obj32)
|
||||
|
||||
obj32 := $(addprefix $(obj)/,$(obj32))
|
||||
|
||||
$(obj)/head.o: AFLAGS-y += -Wa$(comma)-I$(obj)
|
||||
$(obj)/head.o: $(obj32:.32.o=.bin)
|
||||
|
||||
CFLAGS_x86_32 := $(subst -m64,-m32 -march=i686,$(XEN_TREEWIDE_CFLAGS))
|
||||
$(call cc-options-add,CFLAGS_x86_32,CC,$(EMBEDDED_EXTRA_CFLAGS))
|
||||
CFLAGS_x86_32 += -Werror -fno-builtin -g0 -msoft-float -mregparm=3
|
||||
@@ -25,14 +23,66 @@ $(obj32): XEN_CFLAGS := $(CFLAGS_x86_32) -fpic
|
||||
$(obj)/%.32.o: $(src)/%.c FORCE
|
||||
$(call if_changed_rule,cc_o_c)
|
||||
|
||||
orphan-handling-$(call ld-option,--orphan-handling=error) := --orphan-handling=error
|
||||
LDFLAGS_DIRECT-$(call ld-option,--warn-rwx-segments) := --no-warn-rwx-segments
|
||||
LDFLAGS_DIRECT += $(LDFLAGS_DIRECT-y)
|
||||
LD32 := $(LD) $(subst x86_64,i386,$(LDFLAGS_DIRECT))
|
||||
|
||||
%.bin: %.lnk
|
||||
$(OBJCOPY) -j .text -O binary $< $@
|
||||
# The parameters below tweak the generated linker scripts:
|
||||
# - text_gap: padding between .text section external symbols and code.
|
||||
# - text_diff: address of the .text section.
|
||||
#
|
||||
# Note external symbols are only affected by text_diff, while internal symbols
|
||||
# are affected by both text_diff and text_gap. Ensure the sum of gap and diff
|
||||
# is greater than 2^16 so that any 16bit relocations if present in the object
|
||||
# file turns into a build-time error.
|
||||
text_gap := 0x010200
|
||||
text_diff := 0x408020
|
||||
|
||||
%.lnk: %.32.o $(src)/build32.lds
|
||||
$(LD32) -N -T $(filter %.lds,$^) -o $@ $<
|
||||
$(obj)/build32.base.lds: AFLAGS-y += -DGAP=$(text_gap) -DTEXT_DIFF=$(text_diff)
|
||||
$(obj)/build32.offset.lds: AFLAGS-y += -DGAP=$(text_gap) -DTEXT_DIFF=$(text_diff) -DFINAL
|
||||
$(obj)/build32.base.lds $(obj)/build32.offset.lds: $(src)/build32.lds.S FORCE
|
||||
$(call if_changed_dep,cpp_lds_S)
|
||||
|
||||
clean-files := *.lnk *.bin
|
||||
targets += build32.offset.lds build32.base.lds
|
||||
|
||||
# Generate a single 32bit object.
|
||||
#
|
||||
# Resolve any relocations resulting from references between the translation
|
||||
# units. This ensures the same combined object file can be used to generate
|
||||
# multiple images with slightly different linker scripts.
|
||||
$(obj)/built-in-32.tmp.o: $(obj32)
|
||||
$(LD32) -r -o $@ $^
|
||||
|
||||
# Link bundle with a given layout and extract a binary from it.
|
||||
# The linker will allocate GOP and resolve symbols specified in the linker
|
||||
# script.
|
||||
# The conversion to binary avoids polluting global symbols not used externally;
|
||||
# also removes conflict with needed 64 bit GOP.
|
||||
# If possible we use --orphan-handling=error option to make sure we account
|
||||
# for all possible sections from C code.
|
||||
$(obj)/built-in-32.%.bin: $(obj)/build32.%.lds $(obj)/built-in-32.tmp.o
|
||||
$(LD32) $(orphan-handling-y) -N -T $< -Map $(@:bin=map) -o $(@:bin=o) $(filter %.o,$^)
|
||||
$(OBJCOPY) -j .text -O binary $(@:bin=o) $@
|
||||
rm -f $(@:bin=o)
|
||||
|
||||
quiet_cmd_combine = GEN $@
|
||||
cmd_combine = \
|
||||
$(PYTHON) $(srctree)/tools/combine_two_binaries.py \
|
||||
--gap $(text_gap) \
|
||||
--text-diff $(text_diff) \
|
||||
--script $(obj)/build32.offset.lds \
|
||||
--bin1 $(obj)/built-in-32.base.bin \
|
||||
--bin2 $(obj)/built-in-32.offset.bin \
|
||||
--map $(obj)/built-in-32.offset.map \
|
||||
--exports cmdline_parse_early,reloc \
|
||||
--output $@
|
||||
|
||||
targets += built-in-32.S
|
||||
|
||||
# generate final object file combining and checking above binaries
|
||||
$(obj)/built-in-32.S: $(obj)/built-in-32.base.bin $(obj)/built-in-32.offset.bin \
|
||||
$(srctree)/tools/combine_two_binaries.py FORCE
|
||||
$(call if_changed,combine)
|
||||
|
||||
clean-files := built-in-32.*.bin built-in-32.*.map build32.*.lds
|
||||
@@ -15,12 +15,37 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
ENTRY(_start)
|
||||
#ifdef FINAL
|
||||
# undef GAP
|
||||
# define GAP 0
|
||||
# define MULT 0
|
||||
# define TEXT_START
|
||||
#else
|
||||
# define MULT 1
|
||||
# define TEXT_START TEXT_DIFF
|
||||
#endif
|
||||
#define DECLARE_IMPORT(name) name = . + (__LINE__ * MULT)
|
||||
|
||||
ENTRY(dummy_start)
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Merge code and data into one section. */
|
||||
.text : {
|
||||
.text TEXT_START : {
|
||||
/* Silence linker warning, we are not going to use it */
|
||||
dummy_start = .;
|
||||
|
||||
/*
|
||||
* Any symbols used should be declared below, this ensures which
|
||||
* symbols are visible to the 32bit C boot code.
|
||||
* With the exception of using external 32 bit function (maybe
|
||||
* exported by head.S) they should point all to variables.
|
||||
* Attention should be paid to pointers taking care of possible later
|
||||
* relocation.
|
||||
*/
|
||||
DECLARE_IMPORT(__base_relocs_start);
|
||||
DECLARE_IMPORT(__base_relocs_end);
|
||||
. = . + GAP;
|
||||
*(.text)
|
||||
*(.text.*)
|
||||
*(.data)
|
||||
@@ -30,7 +55,11 @@ SECTIONS
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment)
|
||||
*(.comment.*)
|
||||
*(.note.*)
|
||||
}
|
||||
/* Dynamic linkage sections. Collected simply so we can check they're empty. */
|
||||
.got : {
|
||||
*(.got)
|
||||
@@ -18,18 +18,6 @@
|
||||
* Linux kernel source (linux/lib/string.c).
|
||||
*/
|
||||
|
||||
/*
|
||||
* This entry point is entered from xen/arch/x86/boot/head.S with:
|
||||
* - %eax = &cmdline,
|
||||
* - %edx = &early_boot_opts.
|
||||
*/
|
||||
asm (
|
||||
" .text \n"
|
||||
" .globl _start \n"
|
||||
"_start: \n"
|
||||
" jmp cmdline_parse_early \n"
|
||||
);
|
||||
|
||||
#include <xen/compiler.h>
|
||||
#include <xen/kconfig.h>
|
||||
#include <xen/macros.h>
|
||||
|
||||
@@ -759,18 +759,6 @@ trampoline_setup:
|
||||
/* Jump into the relocated trampoline. */
|
||||
lret
|
||||
|
||||
/*
|
||||
* cmdline and reloc are written in C, and linked to be 32bit PIC with
|
||||
* entrypoints at 0 and using the fastcall convention.
|
||||
*/
|
||||
FUNC_LOCAL(cmdline_parse_early)
|
||||
.incbin "cmdline.bin"
|
||||
END(cmdline_parse_early)
|
||||
|
||||
FUNC_LOCAL(reloc)
|
||||
.incbin "reloc.bin"
|
||||
END(reloc)
|
||||
|
||||
ENTRY(trampoline_start)
|
||||
#include "trampoline.S"
|
||||
ENTRY(trampoline_end)
|
||||
|
||||
@@ -12,20 +12,6 @@
|
||||
* Daniel Kiper <daniel.kiper@oracle.com>
|
||||
*/
|
||||
|
||||
/*
|
||||
* This entry point is entered from xen/arch/x86/boot/head.S with:
|
||||
* - %eax = MAGIC,
|
||||
* - %edx = INFORMATION_ADDRESS,
|
||||
* - %ecx = TOPMOST_LOW_MEMORY_STACK_ADDRESS.
|
||||
* - 0x04(%esp) = BOOT_VIDEO_INFO_ADDRESS.
|
||||
*/
|
||||
asm (
|
||||
" .text \n"
|
||||
" .globl _start \n"
|
||||
"_start: \n"
|
||||
" jmp reloc \n"
|
||||
);
|
||||
|
||||
#include <xen/compiler.h>
|
||||
#include <xen/macros.h>
|
||||
#include <xen/types.h>
|
||||
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import functools
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description='Generate assembly file to merge into other code.')
|
||||
auto_int = functools.update_wrapper(lambda x: int(x, 0), int) # allows hex
|
||||
parser.add_argument('--script', dest='script',
|
||||
required=True,
|
||||
help='Linker script for extracting symbols')
|
||||
parser.add_argument('--bin1', dest='bin1',
|
||||
required=True,
|
||||
help='First binary')
|
||||
parser.add_argument('--bin2', dest='bin2',
|
||||
required=True,
|
||||
help='Second binary')
|
||||
parser.add_argument('--gap', dest='gap',
|
||||
required=True,
|
||||
type=auto_int,
|
||||
help='Gap inserted at the start of code section')
|
||||
parser.add_argument('--text-diff', dest='text_diff',
|
||||
required=True,
|
||||
type=auto_int,
|
||||
help='Difference between code section start')
|
||||
parser.add_argument('--output', dest='output',
|
||||
help='Output file')
|
||||
parser.add_argument('--map', dest='mapfile',
|
||||
help='Map file to read for symbols to export')
|
||||
parser.add_argument('--exports', dest='exports',
|
||||
help='Symbols to export')
|
||||
parser.add_argument('--section-header', dest='section_header',
|
||||
default='.section .init.text, "ax", @progbits',
|
||||
help='Section header declaration')
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
gap = args.gap
|
||||
text_diff = args.text_diff
|
||||
|
||||
# Parse linker script for external symbols to use.
|
||||
# Next regex matches expanded DECLARE_IMPORT lines in linker script.
|
||||
symbol_re = re.compile(r'\s+(\S+)\s*=\s*\.\s*\+\s*\((\d+)\s*\*\s*0\s*\)\s*;')
|
||||
symbols = {}
|
||||
lines = {}
|
||||
for line in open(args.script):
|
||||
m = symbol_re.match(line)
|
||||
if not m:
|
||||
continue
|
||||
(name, line_num) = (m.group(1), int(m.group(2)))
|
||||
if line_num == 0:
|
||||
raise Exception("Invalid line number found:\n\t" + line)
|
||||
if line_num in symbols:
|
||||
raise Exception("Symbol with this line already present:\n\t" + line)
|
||||
if name in lines:
|
||||
raise Exception("Symbol with this name already present:\n\t" + name)
|
||||
symbols[line_num] = name
|
||||
lines[name] = line_num
|
||||
|
||||
exports = []
|
||||
if args.exports is not None:
|
||||
exports = dict([(name, None) for name in args.exports.split(',')])
|
||||
|
||||
# Parse mapfile, look for ther symbols we want to export.
|
||||
if args.mapfile is not None:
|
||||
symbol_re = re.compile(r'\s{15,}0x([0-9a-f]+)\s+(\S+)\n')
|
||||
for line in open(args.mapfile):
|
||||
m = symbol_re.match(line)
|
||||
if not m or m.group(2) not in exports:
|
||||
continue
|
||||
addr = int(m.group(1), 16)
|
||||
exports[m.group(2)] = addr
|
||||
for (name, addr) in exports.items():
|
||||
if addr is None:
|
||||
raise Exception("Required export symbols %s not found" % name)
|
||||
|
||||
file1 = open(args.bin1, 'rb')
|
||||
file2 = open(args.bin2, 'rb')
|
||||
file1.seek(0, 2)
|
||||
size1 = file1.tell()
|
||||
file2.seek(0, 2)
|
||||
size2 = file2.tell()
|
||||
if size1 > size2:
|
||||
file1, file2 = file2, file1
|
||||
size1, size2 = size2, size1
|
||||
if size2 != size1 + gap:
|
||||
raise Exception('File sizes do not match')
|
||||
del size2
|
||||
|
||||
file1.seek(0, 0)
|
||||
data1 = file1.read(size1)
|
||||
del file1
|
||||
file2.seek(gap, 0)
|
||||
data2 = file2.read(size1)
|
||||
del file2
|
||||
|
||||
max_line = max(symbols.keys())
|
||||
|
||||
def to_int32(n):
|
||||
'''Convert a number to signed 32 bit integer truncating if needed'''
|
||||
mask = (1 << 32) - 1
|
||||
h = 1 << 31
|
||||
return (n & mask) ^ h - h
|
||||
|
||||
i = 0
|
||||
references = {}
|
||||
internals = 0
|
||||
while i <= size1 - 4:
|
||||
n1 = struct.unpack('<I', data1[i:i+4])[0]
|
||||
n2 = struct.unpack('<I', data2[i:i+4])[0]
|
||||
i += 1
|
||||
# The two numbers are the same, no problems
|
||||
if n1 == n2:
|
||||
continue
|
||||
# Try to understand why they are different
|
||||
diff = to_int32(n1 - n2)
|
||||
if diff == -gap: # this is an internal relocation
|
||||
pos = i - 1
|
||||
print("Internal relocation found at position %#x "
|
||||
"n1=%#x n2=%#x diff=%#x" % (pos, n1, n2, diff),
|
||||
file=sys.stderr)
|
||||
i += 3
|
||||
internals += 1
|
||||
if internals >= 10:
|
||||
break
|
||||
continue
|
||||
# This is a relative relocation to a symbol, accepted, code/data is
|
||||
# relocatable.
|
||||
if diff < gap and diff >= gap - max_line:
|
||||
n = gap - diff
|
||||
symbol = symbols.get(n)
|
||||
# check we have a symbol
|
||||
if symbol is None:
|
||||
raise Exception("Cannot find symbol for line %d" % n)
|
||||
pos = i - 1
|
||||
if args.verbose:
|
||||
print('Position %#x %d %s' % (pos, n, symbol), file=sys.stderr)
|
||||
i += 3
|
||||
references[pos] = symbol
|
||||
continue
|
||||
# First byte is the same, move to next byte
|
||||
if diff & 0xff == 0 and i <= size1 - 4:
|
||||
continue
|
||||
# Probably a type of relocation we don't want or support
|
||||
pos = i - 1
|
||||
suggestion = ''
|
||||
symbol = symbols.get(-diff - text_diff)
|
||||
if symbol is not None:
|
||||
suggestion = " Maybe %s is not defined as hidden?" % symbol
|
||||
raise Exception("Unexpected difference found at %#x "
|
||||
"n1=%#x n2=%#x diff=%#x gap=%#x.%s" % \
|
||||
(pos, n1, n2, diff, gap, suggestion))
|
||||
if internals != 0:
|
||||
raise Exception("Previous relocations found")
|
||||
|
||||
def line_bytes(buf, out):
|
||||
'''Output an assembly line with all bytes in "buf"'''
|
||||
# Python 2 compatibility
|
||||
if type(buf) == str:
|
||||
print("\t.byte " + ','.join([str(ord(c)) for c in buf]), file=out)
|
||||
else:
|
||||
print("\t.byte " + ','.join([str(n) for n in buf]), file=out)
|
||||
|
||||
def part(start, end, out):
|
||||
'''Output bytes of "data" from "start" to "end"'''
|
||||
while start < end:
|
||||
e = min(start + 16, end)
|
||||
line_bytes(data1[start:e], out)
|
||||
start = e
|
||||
|
||||
def reference(pos, out):
|
||||
name = references[pos]
|
||||
n = struct.unpack('<I', data1[pos:pos+4])[0]
|
||||
sign = '+'
|
||||
if n >= (1 << 31):
|
||||
n -= (1 << 32)
|
||||
n += pos
|
||||
if n < 0:
|
||||
n = -n
|
||||
sign = '-'
|
||||
print("\t.hidden %s\n"
|
||||
"\t.long %s %s %#x - ." % (name, name, sign, n),
|
||||
file=out)
|
||||
|
||||
def output(out):
|
||||
prev = 0
|
||||
exports_by_addr = {}
|
||||
for (sym, addr) in exports.items():
|
||||
exports_by_addr.setdefault(addr, []).append(sym)
|
||||
positions = list(references.keys())
|
||||
positions += list(exports_by_addr.keys())
|
||||
for pos in sorted(positions):
|
||||
part(prev, pos, out)
|
||||
prev = pos
|
||||
if pos in references:
|
||||
reference(pos, out)
|
||||
prev = pos + 4
|
||||
if pos in exports_by_addr:
|
||||
for sym in exports_by_addr[pos]:
|
||||
print("\t.global %s\n"
|
||||
"\t.hidden %s\n"
|
||||
"%s:" % (sym, sym, sym),
|
||||
file=out)
|
||||
part(prev, size1, out)
|
||||
|
||||
out = sys.stdout
|
||||
if args.output is not None:
|
||||
out = open(args.output, 'w')
|
||||
print('''/*
|
||||
* File autogenerated by combine_two_binaries.py DO NOT EDIT
|
||||
*/''', file=out)
|
||||
print('\t' + args.section_header, file=out)
|
||||
print('obj32_start:', file=out)
|
||||
output(out)
|
||||
print('\n\t.section .note.GNU-stack,"",@progbits', file=out)
|
||||
out.flush()
|
||||