[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



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.