|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [python] [PATCH v3 1/5] x86/boot: create a C bundle for 32 bit boot code and use it
On Fri, Oct 11, 2024 at 2:17 PM Andrew Cooper <andrew.cooper3@xxxxxxxxxx> wrote:
>
> On 11/10/2024 9:52 am, Frediano Ziglio wrote:
> > diff --git a/xen/arch/x86/boot/Makefile b/xen/arch/x86/boot/Makefile
> > index ff0f965876..4cf0d7e140 100644
> > --- a/xen/arch/x86/boot/Makefile
> > +++ b/xen/arch/x86/boot/Makefile
> > ...
> > +$(obj)/built_in_32.o: $(obj)/built_in_32.other.bin
> > $(obj)/built_in_32.final.bin
> > + $(PYTHON) $(srctree)/tools/combine_two_binaries \
> > + --script $(obj)/build32.final.lds \
> > + --bin1 $(obj)/built_in_32.other.bin --bin2
> > $(obj)/built_in_32.final.bin \
> > + --map $(obj)/built_in_32.final.map \
> > + --exports cmdline_parse_early,reloc \
> > + --section-header '.section .init.text, "ax", @progbits' \
> > + --output $(obj)/built_in_32.s
>
> I can't see a case where we'd want this anywhere other than .init.text,
> so I'd drop the --section-header and just write it out unconditionally.
Could we just change the default in Python code and remove the option
calling the script?
> However, looking at the output:
>
> xen$ head arch/x86/boot/built_in_32.S
> .section .init.text, "ax", @progbits
> .byte 137,194,128,56,0,116,6,64,128,56,0,117,250,41,208,195
> .byte 133,201,116,42,86,83,141,52,8,64,15,182,72,255,66,15
> .byte 182,90,255,56,217,117,15,132,201,116,25,57,198,117,234,184
>
> This wants to start with a comment saying that it was automatically
> generated by combine_two_binaries.
>
Added a
print('''/*
* File autogenerated by combine_two_binaries.py DO NOT EDIT
*/''', file=out)
statement
And renamed the script file adding the ".py".
> Next, we need some kind of symbol at the start. Right now, the
> disassembly reads:
>
> arch/x86/boot/built_in_32.o: file format elf64-x86-64
> Disassembly of section .init.text:
> 0000000000000000 <cmdline_parse_early-0x391>:
> 0: 89 c2 mov %eax,%edx
> 2: 80 38 00 cmpb $0x0,(%eax)
>
>
> because most metadata is lost by this transformation, and it doesn't
> know that this is in fact strlen(). I'd suggest suggest obj32_start: or
> equivalent.
>
Would "obj_start" fine too? Maybe in the future it could be used for
64 bit too (to avoid mistakes like the relocation of error strings we
had).
> > diff --git a/xen/arch/x86/boot/build32.lds b/xen/arch/x86/boot/build32.lds.S
> > similarity index 63%
> > rename from xen/arch/x86/boot/build32.lds
> > rename to xen/arch/x86/boot/build32.lds.S
> > index 56edaa727b..72a4c5c614 100644
> > --- a/xen/arch/x86/boot/build32.lds
> > +++ b/xen/arch/x86/boot/build32.lds.S
> > @@ -15,22 +15,52 @@
> > * with this program. If not, see <http://www.gnu.org/licenses/>.
> > */
> >
> > -ENTRY(_start)
> > +#ifdef FINAL
> > +# define GAP 0
> > +# define MULT 0
> > +# define TEXT_START
> > +#else
> > +# define GAP 0x010200
> > +# define MULT 1
> > +# define TEXT_START 0x408020
> > +#endif
> > +# define DECLARE_IMPORT(name) name = . + (__LINE__ * MULT)
> > +
> > +ENTRY(dummy_start)
> >
> > SECTIONS
> > {
> > - /* Merge code and data into one section. */
> > - .text : {
> > + /* Merge code and read-only data into one section. */
> > + .text TEXT_START : {
> > + /* Silence linker warning, we are not going to use it */
> > + dummy_start = .;
> > +
> > + /* Declare below any symbol name needed.
> > + * Each symbol should be on its own line.
> > + * It looks like a tedious work but we make sure the things we use.
> > + * Potentially they should be all variables. */
> > + DECLARE_IMPORT(__base_relocs_start);
> > + DECLARE_IMPORT(__base_relocs_end);
> > + . = . + GAP;
> > *(.text)
> > *(.text.*)
> > - *(.data)
> > - *(.data.*)
> > *(.rodata)
> > *(.rodata.*)
> > + }
> > +
> > + /* Writeable data sections. Check empty.
> > + * We collapse all into code section and we don't want it to be
> > writeable. */
> > + .data : {
> > + *(.data)
> > + *(.data.*)
> > *(.bss)
> > *(.bss.*)
> > }
> > -
> > + /DISCARD/ : {
> > + *(.comment)
> > + *(.comment.*)
> > + *(.note.*)
> > + }
> > /* Dynamic linkage sections. Collected simply so we can check they're
> > empty. */
> > .got : {
> > *(.got)
> > @@ -64,3 +94,4 @@ ASSERT(SIZEOF(.igot.plt) == 0, ".igot.plt non-empty")
> > ASSERT(SIZEOF(.iplt) == 0, ".iplt non-empty")
> > ASSERT(SIZEOF(.plt) == 0, ".plt non-empty")
> > ASSERT(SIZEOF(.rel) == 0, "leftover relocations")
> > +ASSERT(SIZEOF(.data) == 0, "we don't want data")
>
> 3mdeb are getting around to rebasing/resubmitting the Trenchboot work
> (Intel TXT and AMD SKINIT) backing QubeOS Anti-Evil-Maid.
>
> While most of the cleanup is proving very helpful (i.e. reducing their
> work), the lack of .data was seen as likely to be a blocker. Thinking
> about this more, I'm now fairly certain we do not need to exclude data.
>
We could change if needed in the future.
Can I order:
- .text
- .rodata
- .data
- .bss
instead of old
- .text
- .data
- .rodata
- .bss
So all readonly code/data are more close?
> This object executes during boot in 32bit flat unpaged mode (i.e. there
> are no actual restrictions during execution), and because it's all
> wrapped in .init.text, its "just code" to the outside world. This means
> it does not impact R^X that we're trying to arrange for the EFI section
> headers.
>
> Therefore the data arrangements should stay as they were before, and I
> think the result will be fine. We obviously don't want gratuitous use
> of .data, but we don't need to prohibit it either.
>
> I've got various other suggestions for improvements of the result, but
> they can all be deferred until later. This is complicated enough.
>
> > diff --git a/xen/tools/combine_two_binaries b/xen/tools/combine_two_binaries
> > new file mode 100755
> > index 0000000000..ea2d6ddc4e
> > --- /dev/null
> > +++ b/xen/tools/combine_two_binaries
> > @@ -0,0 +1,198 @@
> > +#!/usr/bin/env python3
> > +
> > +from __future__ import print_function
> > +import argparse
> > +import re
> > +import struct
> > +import sys
> > +
> > +parser = argparse.ArgumentParser(description='Generate assembly file to
> > merge into other code.')
> > +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('--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='.text',
> > + help='Section header declaration')
> > +parser.add_argument('-v', '--verbose',
> > + action='store_true')
> > +args = parser.parse_args()
> > +
> > +gap = 0x010200
> > +text_diff = 0x408020
> > +
> > +# Parse linker script for external symbols to use.
> > +symbol_re =
> > re.compile(r'\s+(\S+)\s*=\s*\.\s*\+\s*\((\d+)\s*\*\s*0\s*\)\s*;')
>
> What is this looking for? I'd expect the DECLARE_IMPORT() lines, but
> this is as clear as regexes...
>
Added a
# Next regex matches expanded DECLARE_IMPORT lines in linker script.
comment
> > +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')
> > +
> > +file1.seek(0, 0)
> > +data1 = file1.read(size1)
> > +file2.seek(gap, 0)
> > +data2 = file2.read(size1)
> > +
> > +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),
>
> Here and elsewhere, you don't need brackets around around the string
> itself. Python strings are like C strings and will auto-concatenate.
>
Done (also another similar occurency below).
> > + 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))
Here removed other parenthesis
> > +if internals != 0:
> > + raise Exception("Previous relocations found")
> > +
> > +def line_bytes(buf, out):
> > + '''Output an assembly line with all bytes in "buf"'''
> > + 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)
>
> I'm guessing this is Py2/3 compatibility?
>
Yes, as added
# Python 2 compatibility
will state
> > +
> > +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),
>
> Personally, I think this is easier to read as:
>
> print("\t.hidden %s\n"
> "\t.long %s %s %#x - ." % (name, name, sign, n),
> file=out)
>
> so it visually matches the output too. Same for .globl/hidden lower.
>
Done
> ~Andrew
Frediano
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |