Page MenuHomePhabricator

[llvm-objcopy] Add support for --gap-fill and --pad-to options
Needs ReviewPublic

Authored by asmith on Sep 17 2019, 8:42 PM.

Details

Summary

This adds two new options to match GNU objcopy:

--gap-fill is used to fill gaps between two adjacent loadable sections with a specified 8 bit value.
--pad-to will pad the last loadable section to the pad-to address with the value specified by --gap-fill if present or zero by default.

Overview of how this works in llvm-objcopy:

  1. Resize all the segments if needed.
  2. Adjust section/segment layout (i.e. file offset) due to overlap across sections in the same segment or overlap across segments.
  3. Set the parent segment's content.
  4. Set the segment child's content.
  5. Set the section's content.

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

Any comments from the llvm-objcopy community?

Commented regarding tests and nits. Could you update docs/CommandGuide/llvm-objcopy.rst as well?

llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
5

Is it possible to use yaml2obj instead of adding a binary file? I think we just need few sections to make sure that this patch works well. For example (not tested):

!ELF
FileHeader:
  Class:           ELFCLASS64
  Data:            ELFDATA2LSB
  Type:            ET_EXEC
  Machine:         EM_X86_64
Sections:
  - Name:            .text
    Type:            SHT_PROGBITS
    Content:         "AABBCCDD"
  - Name:            .foo
    Type:            SHT_PROGBITS
    Content:         "EEFF"
  - Name:            .bar
    Type:            SHT_NOBITS
llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
662

nit: please avoid auto and use uint64_t.

663

ditto

672

ditto

asmith updated this revision to Diff 221229.Sep 22 2019, 12:23 PM

Address reviewers comments about auto

asmith updated this revision to Diff 221232.Sep 22 2019, 12:31 PM

Add new options to objcopy documentation

asmith marked 3 inline comments as done.Sep 22 2019, 12:35 PM
asmith added inline comments.
llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
5

That's a good suggestion. Will look into that.

Hui added inline comments.Sep 22 2019, 8:49 PM
llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
5

This patch resizes the section and its containing segments. (Parent or child segment). One main part of the test is to check the section to segment mapping from PHDR. However yam2obj can't keep program headers.

Thinks for the patch! I haven't had time to review the meat of it yet, but will try to get back to it in the next couple of days. One quick question in the meantime: what's the motivation behind this? Is it to improve GNU compatibility?

llvm/docs/CommandGuide/llvm-objcopy.rst
106

I think you can change this slightly to "... to the ''<address>'' ...", using the formatting of the reference to <section> in --only-section above (i.e. back ticks instead of apostrophes).

107

:option:''--gap-fill'' (using backticks instead of apostrophes)

llvm/include/llvm/Object/ELF.h
408 ↗(On Diff #221232)

This and a couple of tests look like an unrelated change?

llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
2

Why does this and the other tests require shell? I don't see anything in here that requires it (od and cmp are both available in GnuWin32 tools for example).

3

We tend in the newer binary tools tests to use '##' for comments to separate them from test commands. Please could you update.

5

This patch resizes the section and its containing segments. (Parent or child segment). One main part of the test is to check the section to segment mapping from PHDR. However yam2obj can't keep program headers.

I'm not sure I understand what you mean here? yaml2obj supports program headers (it has done for a couple of years now I think). See for example llvm/test/tools/yaml2obj/program-header.yaml etc as well as various llvm-objcopy tests using program headers (e.g. llvm/test/tools/llvm-objcopy/segment-test-remove-section.test).

llvm/tools/llvm-objcopy/CopyConfig.cpp
632

addr -> Addr

I'm guessing this and the above error return Expected. Would you mind not using auto here to make it clear, please?

Hui added inline comments.Sep 23 2019, 10:20 AM
llvm/include/llvm/Object/ELF.h
408 ↗(On Diff #221232)

This diagnostic message probably emitted when the layout of resized sections was not correctly done. However it is misleading. The 'Size' is supposed to be in hex while it is not.

asmith marked an inline comment as done.Sep 23 2019, 11:32 AM

Thinks for the patch! I haven't had time to review the meat of it yet, but will try to get back to it in the next couple of days. One quick question in the meantime: what's the motivation behind this? Is it to improve GNU compatibility?

I need these options to use llvm-objcopy when building uboot.

llvm/include/llvm/Object/ELF.h
408 ↗(On Diff #221232)

This is a bug fix. The value wasn’t printed correctly.

Hui added inline comments.Sep 23 2019, 2:51 PM
llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
5

You are right. Actually I was then thinking about using obj2yaml to get yaml file and operate it with yaml2obj in the test. Unfortunately obj2yaml hasn't supported dumping the program headers (A plausible easy feature to have but not in position yet). So no output about PHDR by yam2obj.

asmith updated this revision to Diff 221461.Sep 23 2019, 9:11 PM

Address comments on comment character #, auto and rst file.

asmith marked 7 inline comments as done.Sep 23 2019, 9:12 PM

One major question to answer is how should --gap-fill interact with the preserving of segment contents that llvm-objcopy follows when segment contents are not covered by existing sections (see llvm/test/tools/llvm-objcopy/preserve-segment-contents.test for an example of the behaviour). I'd be inclined to have --gap-fill overwrite the data in this case, in which case a test should be written to show that the old data is no longer written in this case (see below for more details).

Some other questions/comments:

  1. What should be the behaviour for gaps between the start of a segment and the start of the first section? I feel like --gap-fill should fill that gap too (same goes for end of the section and segment).
  2. In relation to the preserve contents issue above, what should happen if a section is removed? Currently it fills it with zeroes. I think it should instead fill it with --gap-fill bytes (see writeSegmentData).
  3. For --gap-fill, I think a simpler approach than what you have might be to change how Segments are written in writeSegmentData: if --gap-fill is not specified, use the existing contents of the segment, otherwise use --gap-fill's value.
  4. For --pad-to, what should happen with SHT_NOBITS sections? The description you've written suggests that they should have their size increased to pad to the corresponding address, but this isn't the behaviour I see with GNU objcopy.
  5. Also for --pad-to, I feel like your code is more complicated than it needs to be. I feel like the relevant changes would be most appropriate in or immediately before layoutSegments - identify what the last segment is and then resize it and the last section in it. I don't think you need to create new sections or segments, although you'll need to identify how you can increase the size.
  6. What does GNU objcopy do for non-progbits sections that are last in the loadable segment, e.g. .dynamic sections?
  7. What does GNU objcopy do if the last such section is nested inside multiple segments (e.g. because it's a TLS section)? I expect it to resize all such segments, which might not be trivial to achieve using llvm-objcopy's current architecture (sections are only in the top-most parent segment, and don't know about other segments they are in).
llvm/include/llvm/Object/ELF.h
408 ↗(On Diff #221232)

I agree it's a bug fix, but it's a bug fix not directly related to this issue. Please create a separate patch for it.

llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
5

In general, we prefer to create the YAML file by hand, as obj2yaml output for an object is significantly more verbose than is required to test the behaviour. If after looking at existing examples of how to write program headers for yaml2obj, you need more assistance in creating a test input, I'm happy to help - I've had plenty of experience crafting them to test odd cases.

llvm/tools/llvm-objcopy/ObjcopyOpts.td
238

This line is too long and should be broken up over multiple lines, like the other switch names. I think there's a clang-format invocation to do that for tablegen files, but I'm not sure what it is.

--gap-fill should likely overwrite all interstitial data which includes data at the end of loadable segments not just the data between sections that we previously preserved. I view the --gap-fill use case as being a use case for things like lld's trap filling. Yes this violates the strict interpretation of "don't overwrite segment contents" but its ok in this case I think. Making binaries more secure is a good thing!

--pad-to is more tricky and requires altering the segment size which I'm not so fond of. We have to ensure that sections never fall outside of a segment or overlap another section when this happens.

What's the use case for these two flags? Where has it come up in actual practice that these were needed? I think they basically exist to cover an issue left by the linker that lld no longer leaves. I'm particularly hesitant to add --pad-to

"Interstitial data" is data covered by by a segment but not a section. Gap fill should IMO set the bytes for all of that space. This is consistent with James's recommendation how things work. --pad-to when padding to a page aligned address this can for instance ensure that all executable bytes contain either valid code or a trap instruction.

Another axiom I'd like to hold if possible for the use case: no new segment overlapping should occur and its an error if --pad-to causes this. Technically this is valid and can occur and the internals handle it so I'm not adding a hard no here.

Dear god lets throw an error for now if this has to touch a TLS segment/sections...thats a crazy rabit whole where the rules for how everything works change. When someone hits that case we can think about it then.

If the last PT_LOAD segment memsize and filesize don't agree then using -pad-to is invalid and should be be an error.

I'm still not satisfied I've thought of all the error conditions so expect more as I think about this more carefully.

llvm/tools/llvm-objcopy/ObjcopyOpts.td
233

I'd say "set all bytes not covered by an allocated section but covered by a PT_LOAD segment to <val>" or something explicit to that effect. Reading GNU objcopy's format agnostic documentation is far too vauge to guess the results of.

238

We should be explict and say "pad the last SHF_ALLOC section ...."

Hui added a comment.Nov 13 2019, 8:51 AM

Some observations on the following sample sample.o with GNU objcopy.

Seg1:                      0x108   0x10F
                                 |  .gap1  |

Seg2: 0x100       0x102    0x108   0x10F        0x110   0x114      0x116
       | .space1   | .nogap | .gap1 |  .space2   | .gap2 |  .space3 |

Seg3: 0x200     0x204    0x20A        0x212
       | .space4 |  .foo  |            | .nobit_bss |

Seg4: 0x200                              0x218
       | .nobit_tbss                      |

One major question to answer is how should --gap-fill interact with the preserving of segment contents that llvm-objcopy follows when segment contents are not covered by existing sections (see llvm/test/tools/llvm-objcopy/preserve-segment-contents.test for an example of the behaviour). I'd be inclined to have --gap-fill overwrite the data in this case, in which case a test should be written to show that the old data is no longer written in this case (see below for more details).

Some other questions/comments:

  1. What should be the behaviour for gaps between the start of a segment and the start of the first section? I feel like --gap-fill should fill that gap too (same goes for end of the section and segment).

GNU filled .space2, .space3 and .space4, leaving .space1 and spaces at 0x20A -0x212 unchanged. I think it's exactly what GNU objcopy means for filling gaps between loadable sections. We could fill .space1 (if llvm-objcopy specific).

  1. In relation to the preserve contents issue above, what should happen if a section is removed? Currently it fills it with zeroes. I think it should instead fill it with --gap-fill bytes (see writeSegmentData).

This is a concern. Currently with the following command. the -R option will overwrite --gap-fill. You are right. The main reason is that the removal of sections is done in writeSegmentData after the --gap-fill patch.
llvm-objcopy.exe sample.o sf --regex -R .space.* --gap-fill=0xe9

  1. For --gap-fill, I think a simpler approach than what you have might be to change how Segments are written in writeSegmentData: if --gap-fill is not specified, use the existing contents of the segment, otherwise use --gap-fill's value.

Since the section and segment could be resized via --gap-fill, their file offsets need to be recalculated ahead of writeSegmentData, i.e via layoutSegmeent etc.

  1. For --pad-to, what should happen with SHT_NOBITS sections? The description you've written suggests that they should have their size increased to pad to the corresponding address, but this isn't the behaviour I see with GNU objcopy.
  1. Also for --pad-to, I feel like your code is more complicated than it needs to be. I feel like the relevant changes would be most appropriate in or immediately before layoutSegments - identify what the last segment is and then resize it and the last section in it. I don't think you need to create new sections or segments, although you'll need to identify how you can increase the size.
  2. What does GNU objcopy do for non-progbits sections that are last in the loadable segment, e.g. .dynamic sections?
  1. What does GNU objcopy do if the last such section is nested inside multiple segments (e.g. because it's a TLS section)? I expect it to resize all such segments, which might not be trivial to achieve using llvm-objcopy's current architecture (sections are only in the top-most parent segment, and don't know about other segments they are in).

@asmith, are you still working on this patch? The gap-fill feature is definitely needed for parity with existing objcopy implementations.

asmith updated this revision to Diff 288377.Aug 27 2020, 10:03 AM

@srhines Not at the moment. I updated the PR with the changes I have locally.

jhenderson added inline comments.Sep 2 2020, 12:59 AM
llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
2

"regression tests" means different things to different people. Simply "tests" is sufficient here.

21

You don't need to explicitly create a YAML with sections that get stripped to create the gaps in them. You can use the yaml2obj "Fill" pseudo-section type. It works the same way as regular sections, but no section header is created in the output for it, so the output looks like it has gaps. You just list it as a Section, with the required size in the Sections list, and Type: Fill. See llvm\test\tools\yaml2obj\ELF\custom-fill.yaml for example usage.

42
181–185

It would be easier to follow the test if you put these checks up where they are used for the first time.

llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
687

Nit: don't forget to run clang-format on the code you modify.

asmith updated this revision to Diff 304960.Nov 12 2020, 1:38 PM

addressed reviewer comments

asmith marked 8 inline comments as done.Nov 12 2020, 1:41 PM

I'm out of time to review this further, but there's plenty of comments to take a look at. Also adding a couple more reviewers who might be able to help out.

llvm/docs/CommandGuide/llvm-objcopy.rst
84

Presumably you meant bytes, not bits?

109–110
llvm/test/tools/llvm-objcopy/ELF/gap-fill.test
4
7
21

This tends to be preferred in newer tests.

23

I'm not convinced this sanity test is worthwhile, since that just shows that yaml2obj or llvm-readelf are broken, whereas this is a test for llvm-objcopy.

35–36

You might also want a variant of this test with --gap-fill last.

47
48

What's the point of this line?

49

It's not clear in what way this is truncated. You need to show the actual behaviour here too. You could do this by picking a value that truncates to the same value as a "normal" test and then using cmp to show that the output is identical. The "normal" test output will still need verifying to show the right output has been emitted.

54–55

And then change SHDR/PHDR below to CHECK. (Note: I can't remember whether program headers or section headers appear first - you may need to change the order of the two check blocks around to match).

It is not obvious from the comments in this test or the documentation that --gap-fill changes the size of sections. This is actually slightly strange behaviour to me. Are you sure it reflects what GNU objcopy does? If it is, please improve the documentation to say that the filling is achieved by padding the tail of sections, and probably also explain what is expected in the test comments.

58–66

The majority of this data is not relevant to the test. I suggest removing the columns that aren't important. For those at the start or end, you can simply omit them. For those in between columns you want to check (name, offset, size probably), you can just use {{.*}} to indicate a regex matching anything.

68

This comment appears to be referring to a potential problem with GNU objcopy? If so, it's not really relevant, and the comment could rot if the behaviour in GNU objcopy is fixed/changes.

93

Don't you also need to check that the value is correct?

95

I think it would be a good idea to show how a multi-byte value is written too, especially in relation to being truncated if the gap size isn't a multiple of the specified value.

99

Make sure to add full stops to the end of all comments.

118

Don't use grep in tests. See https://llvm.org/docs/TestingGuide.html#writing-new-regression-tests.

Could you just arrange your sections to be closer together so that you don't need such a large amount to check? You could then easily check using od.

123–138

We usually try to remove excessive space between the key and value in YAML, to make it a little easier to read (the edit suggestion is just partial, the same sort of edit applies throughout). You also don't need this many fields - AddressAlign defaults to 0/1 (I forget which, but they are semantically the same here), Size is auto-derived from the Content if not specified, and Address is automatically calculated based on the segment VAddr, IIRC. Get rid of the excess here and below.

141

The SHF_EXECINSTR is not relevant to the test, so get rid of it to make the test simpler. Same goes below - the TLS bit is probably not relevant to the test (NOBITS might be on the other hand).

165
186

Do you actually need any content here? It's not clear what the purpose of this section is.

188–194

This generates illegal ELF. The gABI dictates that PT_LOAD segments must be in ascending address order. Please move this segment accordingly.

llvm/test/tools/llvm-objcopy/ELF/pad-to.test
1

Looks like this test is missing a comment explaining the purpose of the test.

2
4

Why? (Please expand the comment to explain the reason)

26

How about --pad-to=0x204 for testing the edge case?

44–46

See my comment about not using grep in the --gap-fill test.

51
53

As above - don't use grep.

57–59

This can be combined into a single command, as suggested in the other test.

76

Same comments as gap-fill.test re. reducing what's in the YAML to a more minimal amount.

llvm/tools/llvm-objcopy/CopyConfig.cpp
629
630–631

This needs testing.

633

Are you sure this is supposed to be truncated to a uint8_t and not say a 4-byte word or similar that is repeated?

llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
626–633

You need testing to show why this sorting is necessary.

647–650

This isn't the way to do this. A) as the name implies, OriginalData is intended to be immutable (maybe it should be const). Changing it changes llvm-objcopy's view of what the section originally had for data. B) This looks like a memory leak - you new an array and then create an ArrayRef to it, but there's nothing cleaning up the memory afterwards.

Take a look at OwnedDataSection. It's likely you'll want something like this. Alternatively, you could add a field to the SectionBase class called "AddedData" or something like that which is written after the rest of the section's data.

On a related note, beware of other section types like dynamic sections. I don't know if GNU objcopy changes the size of such sections. If it does, we'll need to too, but the logic for such sections is different in llvm-objcopy, so may need additional handling and testing.