Bug 11009

Summary: patchelf inflates filesize and causes performance problems
Product: [Build System, Metadata & Runtime] OE-Core Reporter: Richard Purdie <richard.purdie>
Component: devtools / tool chainAssignee: Stephano Cetola <stephano>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: Medium+ CC: meta.mr.watcher, meta.watcher, randy.macleod
Version: 2.2   
Target Milestone: 2.3 M3   
Hardware: x86   
OS: Multiple   
Whiteboard:
OS type for building Yocto: --- Type of Regression: ---
Verified: Documentation change: No (bug/feature does not impact docs)
Bug Depends on: 11123    
Bug Blocks:    
Attachments:
Description Flags
Patch for the kernel to allow holes in PT_LOAD sections, fixing the bug patchelf referrs to none

Description Richard Purdie 2017-02-02 17:27:48 UTC
When we switched to recipe specific sysroots (rss), performance took a nose dive. Its easy to blame rss but it turns out not to be entirely at fault.

Three configurations are compared here:

a) Pre-RSS (revision 45df694a9f472ac2f684aadac4d864c3dfdc48a7)
b) Post-RSS (revision 226a508da955439b881b2f0a544a3aee76e59919)
c) as b) with a tweak to patchelf

Overall build times:

a) 22794.25user 2687.88system 30:32.84elapsed 1390%CPU (0avgtext+0avgdata 919056maxresident)k
b) 22677.25user 3238.79system 36:16.68elapsed 1190%CPU (0avgtext+0avgdata 918896maxresident)k
c) 23571.84user 3383.65system 31:36.83elapsed 1421%CPU (0avgtext+0avgdata 919068maxresident)k

For the overall build and sstate directories, du -s shows:
a)
3992588	build-pre-rss/sstate-cache
30804484	build-pre-rss/tmp
b)
4013272	build-with-rss/sstate-cache
36519084	build-with-rss/tmp
c)
4014744	build-with-rss2/sstate-cache
35336960	build-with-rss2/tmp

However more worryingly:

$ du -s build-pre-rss/tmp/sysroots/
2506092	build-pre-rss/tmp/sysroots/
$ du -s build-with-rss/tmp/sysroots-components/
3790712	build-with-rss/tmp/sysroots-components/
$ du -s build-with-rss2/tmp/sysroots-components/
2467544	build-with-rss2/tmp/sysroots-components/

These numbers *should* be equivalent but as you can see, b) is ~1.2GB larger. The reason turned out to be patchelf.

Taking a specific binary from a specific recipe, bc from bc-native, in a) its 82kb (stripped) yet in b) its 2.17MB.

$ ./patchelf --set-interpreter /bin/rp bc
warning: working around a Linux kernel bug by creating a hole of 2084864 bytes in ‘bc’

https://github.com/NixOS/patchelf/blob/master/src/patchelf.cc#L710 shows that this "hole" is just padded zeros using memset, its not a proper sparse hole.

My quick hack for c) is:

Subject: Make patchelf files sparse

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>

diff --git a/meta/classes/uninative.bbclass b/meta/classes/uninative.bbclass
index 410fb72..0d1063a 100644
--- a/meta/classes/uninative.bbclass
+++ b/meta/classes/uninative.bbclass
@@ -121,11 +121,8 @@ python uninative_changeinterp () {
             if not elf.isDynamic():
                 continue
 
-            try:
-                subprocess.check_output(("patchelf-uninative", "--set-interpreter",
-                                         d.getVar("UNINATIVE_LOADER"), f),
-                                        stderr=subprocess.STDOUT)
-            except subprocess.CalledProcessError as e:
-                bb.fatal("'%s' failed with exit code %d and the following output:\n%s" %
-                         (e.cmd, e.returncode, e.output))
+            subprocess.check_output(("patchelf-uninative", "--set-interpreter", d.getVar("UNINATIVE_LOADER"), f), stderr=subprocess.STDOUT)
+            subprocess.check_output(("cp", "--sparse=always", f, f + ".sparse"), stderr=subprocess.STDOUT)
+            os.unlink(f)
+            os.rename(f + ".sparse", f)
 }

which after patchelf does a cp --sparse=always on the file to turn the padding into sparseness, after which we get the performance back which seems linked to sysroot sizes.

Ideally, rather than my hack, we'd teach patchelf to seek() when it writes out the file, creating sparseness out the box and removing the need for my workaround. We'd need to ship a new uninative with the new patchelf to fix this all properly. In the meantime given the performance improvement, I'm tempted to add my workaround.
Comment 1 Stephano Cetola 2017-02-12 17:13:48 UTC
I think the fix here is to store the hole size and location, then call a function from writeFile that uses lseek, at least that's my running theory. A couple questions:

1. Why are we producing binary executables with the e_type set to DYN?
2. PatchELF is working around a kernel bug, but doesn't mention the actual bug it is working around. Any ideas, or shall I assume that is a rabbit hole?
Comment 2 Richard Purdie 2017-03-06 11:51:49 UTC
(In reply to comment #1)
> I think the fix here is to store the hole size and location, then call a
> function from writeFile that uses lseek, at least that's my running theory.
> A couple questions:
> 
> 1. Why are we producing binary executables with the e_type set to DYN?
> 2. PatchELF is working around a kernel bug, but doesn't mention the actual
> bug it is working around. Any ideas, or shall I assume that is a rabbit hole?

For 1, see bug 11123 which I added as a DEPENDS. These two issues appear related.
Comment 3 Richard Purdie 2017-03-06 15:49:51 UTC
(In reply to comment #2)
> (In reply to comment #1)
> > I think the fix here is to store the hole size and location, then call a
> > function from writeFile that uses lseek, at least that's my running theory.
> > A couple questions:
> > 
> > 1. Why are we producing binary executables with the e_type set to DYN?
> > 2. PatchELF is working around a kernel bug, but doesn't mention the actual
> > bug it is working around. Any ideas, or shall I assume that is a rabbit hole?

The bug seems to be described in:
https://github.com/NixOS/patchelf/commit/1a64cf7edc39b93a37beb0665de8f0c6cab90f51
Comment 4 Richard Purdie 2017-03-06 17:10:57 UTC
To update further, the kernel bug described here does still exist, I was able to test/reproduce it.
Comment 5 Richard Purdie 2017-03-07 00:09:13 UTC
Created attachment 3643 [details]
Patch for the kernel to allow holes in PT_LOAD sections, fixing the bug patchelf referrs to
Comment 6 Richard Purdie 2017-03-07 20:53:52 UTC
The fix for #11123 also fixes this since it removes the hole code:

http://git.yoctoproject.org/cgit.cgi/poky/commit/?id=c4901328fe5cf912c0965e5b011b64a95a9bcb9d