diff options
author | klondike <klondike@xiscosoft.es> | 2010-11-12 18:32:30 +0100 |
---|---|---|
committer | klondike <klondike@xiscosoft.es> | 2010-11-12 18:32:30 +0100 |
commit | b499b0f83177293b546cc99aaf3319d72c7add67 (patch) | |
tree | c28adcf00d43475234f06967b2db43ce2cb1fe67 /html/pic-fix-guide.html | |
parent | merging with CVS tree (diff) | |
download | hardened-docs-b499b0f83177293b546cc99aaf3319d72c7add67.tar.gz hardened-docs-b499b0f83177293b546cc99aaf3319d72c7add67.tar.bz2 hardened-docs-b499b0f83177293b546cc99aaf3319d72c7add67.zip |
updating html previews
Diffstat (limited to 'html/pic-fix-guide.html')
-rw-r--r-- | html/pic-fix-guide.html | 877 |
1 files changed, 877 insertions, 0 deletions
diff --git a/html/pic-fix-guide.html b/html/pic-fix-guide.html new file mode 100644 index 0000000..179eab0 --- /dev/null +++ b/html/pic-fix-guide.html @@ -0,0 +1,877 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<link title="new" rel="stylesheet" href="http://www.gentoo.org/css/main.css" type="text/css"> +<link REL="shortcut icon" HREF="http://www.gentoo.org/favicon.ico" TYPE="image/x-icon"> +<link rel="search" type="application/opensearchdescription+xml" href="http://www.gentoo.org/search/www-gentoo-org.xml" title="Gentoo Website"> +<link rel="search" type="application/opensearchdescription+xml" href="http://www.gentoo.org/search/forums-gentoo-org.xml" title="Gentoo Forums"> +<link rel="search" type="application/opensearchdescription+xml" href="http://www.gentoo.org/search/bugs-gentoo-org.xml" title="Gentoo Bugzilla"> +<link rel="search" type="application/opensearchdescription+xml" href="http://www.gentoo.org/search/packages-gentoo-org.xml" title="Gentoo Packages"> +<link rel="search" type="application/opensearchdescription+xml" href="http://www.gentoo.org/search/archives-gentoo-org.xml" title="Gentoo List Archives"> +<title>Gentoo Linux Documentation +-- + HOWTO Locate and Fix .text Relocations (TEXTRELs)</title> +</head> +<body style="margin:0px;" bgcolor="#ffffff"><table width="100%" border="0" cellspacing="0" cellpadding="0"> +<tr><td valign="top" height="125" bgcolor="#45347b"><a href="http://www.gentoo.org/"><img border="0" src="http://www.gentoo.org/images/gtop-www.jpg" alt="Gentoo Logo"></a></td></tr> +<tr><td valign="top" align="right" colspan="1" bgcolor="#ffffff"><table border="0" cellspacing="0" cellpadding="0" width="100%"><tr> +<td width="99%" class="content" valign="top" align="left"> +<br><h1>HOWTO Locate and Fix .text Relocations (TEXTRELs)</h1> +<form name="contents" action="http://www.gentoo.org"> +<b>Content</b>: + <select name="url" size="1" OnChange="location.href=form.url.options[form.url.selectedIndex].value" style="font-family:sans-serif,Arial,Helvetica"><option value="#doc_chap1">1. Introduction</option> +<option value="#doc_chap2">2. Finding broken object code</option> +<option value="#doc_chap3">3. Dissecting broken object code</option> +<option value="#doc_chap4">4. Finding the broken source code</option> +<option value="#doc_chap5">5. How to write PIC (in theory)</option> +<option value="#doc_chap6">6. Cookie cutter PIC fixes</option> +<option value="#doc_chap7">7. How to fix broken PIC (in practice)</option> +<option value="#doc_chap8">8. References</option></select> +</form> +<p class="chaphead"><a name="doc_chap1"></a><span class="chapnum">1. + </span>Introduction</p> +<p> +You should make sure to read the <a href="pic-guide.xml">Introduction to +Position Independent Code</a> before tackling this guide. +</p> +<p> +This guide is x86-centric for now. The reason being, the majority of broken +object files are due to poorly written x86 assembly stemming from the simple +fact that the x86 architecture has so few registers. Other architectures have +a large enough register set that they can reserve a register as the "PIC +register" without incurring a performance hit. Every architecture has to be +mindful of PIC and its implications, x86 just happens to be the dominant +architecture at the moment in the 'desktop' world of open source. +</p> +<p> +We will update for non-x86 as we aquire details and useful examples. +</p> +<p class="chaphead"><a name="doc_chap2"></a><span class="chapnum">2. + </span>Finding broken object code</p> +<p> +Before you can start fixing something, you got to make sure it's broken first, +right? For this reason, we've developed a suite of tools named <a href="http://www.gentoo.org/proj/en/hardened/pax-utils.xml">PaX Utilities</a>. If you are not +familiar with these utilities, you should read the <a href="http://www.gentoo.org/proj/en/hardened/pax-utils.xml">PaX Utilities Guide</a> now. Gentoo +users can simply do <span class="code" dir="ltr">emerge pax-utils</span>. Non-Gentoo users should be able +to find a copy of the source tarball in the <span class="path" dir="ltr">distfiles</span> on a <a href="http://www.gentoo.org/main/en/mirrors.xml">Gentoo Mirror</a>. Once you have the PaX +Utilities setup on your system, we can start playing around with +<span class="code" dir="ltr">scanelf</span>. +</p> +<p> +Keep in mind that although these utilities are named PaX Utilities, they +certainly do not require PaX or anything else like that on your system. +The name is a historical artifact and want of a better name, has stuck. +</p> +<p> +Let's see if your system has any broken files. +</p> +<a name="doc_chap2_pre1"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing2.1: Scan your system</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">scanelf -lpqt</span> +TEXTREL /usr/lib/opengl/xorg-x11/lib/libGL.so.1.2 +TEXTREL /usr/lib/libSDL-1.2.so.0.7.2 +TEXTREL /usr/lib/libdv.so.4.0.2 +TEXTREL /usr/lib/libsmpeg-0.4.so.0.1.3 +TEXTREL /usr/lib/libOSMesa.so.4.0 +TEXTREL /usr/lib/libxvidcore.so.4.1 +</pre></td></tr> +</table> +<p> +Ideally, scanelf should not display anything, but on an x86 system, this is +rarely the case. Here we can see six libraries with TEXTRELs in them. +To quickly find out what package these files come from, Gentoo users can +<span class="code" dir="ltr">emerge portage-utils</span> and use <span class="code" dir="ltr">qfile</span>. +</p> +<a name="doc_chap2_pre2"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing2.2: Determine the broken packages</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">qfile `scanelf -qylpF%F#t`</span> +media-libs/libdv (/usr/lib/libdv.so.4.0.2) +media-libs/libsdl (/usr/lib/libSDL-1.2.so.0.7.2) +media-libs/smpeg (/usr/lib/libsmpeg-0.4.so.0.1.3) +media-libs/xvid (/usr/lib/libxvidcore.so.4.1) +x11-base/xorg-x11 (/usr/lib/opengl/xorg-x11/lib/libGL.so.1.2) +x11-base/xorg-x11 (/usr/lib/libOSMesa.so.4.0) +</pre></td></tr> +</table> +<p> +Now that we know the offenders, we have a choice. We can file a bug upstream +(who generally don't care unless you can provide a fix), file a bug in the +<a href="http://bugs.gentoo.org/">Gentoo Bugzilla</a> (which is a nice +lazy cop out), or we can fix it ourselves (that is why you're reading this +guide right?). You should double check that the package version you have +installed is the latest upstream has to offer and the latest version your +distro has to offer. Who knows, maybe you can get lucky and someone else has +already fixed it. If you wish to get feedback on your work, feel free to +contact the <a href="mailto:hardened@gentoo.org">Gentoo hardened team</a>. +</p> +<p class="secthead"><a name="doc_chap2_sect2">"False" Positives</a></p> +<p> +Sometimes you may come across a package which contains a mountain of TEXTRELs +with seemingly no relation to assembler. This may simply be because the +objects were not properly compiled with the appropriate PIC flag. The fix is +quite simple: make sure every object file that is linked into the final shared +object is compiled with the appropriate PIC flag (typically -fPIC). +</p> +<p> +For example, let's look at the silc-plugin package. It builds up a few +modules, but only compiles some of the objects with -fPIC that are linked into +the final libsilc_core.so module. The output of scanelf here is quite +extensive! +</p> +<a name="doc_chap2_pre3"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing2.3: Run scanelf on silc-plugin</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">scanelf -qT /usr/lib/irssi/modules/libsilc_core.so | wc -l</span> +10734 +$ <span class="code-input">scanelf -qT /usr/lib/irssi/modules/libsilc_core.so</span> +... + libsilc_core.so: silc_client_ftp_ask_name [0xD542C] in silc_client_receive_new_id [0xD5380] + libsilc_core.so: silc_client_run_one [0xD55CA] in silc_client_receive_new_id [0xD5380] + libsilc_core.so: silc_id_payload_parse [0xD5842] in silc_client_packet_parse_type [0xD57B0] + libsilc_core.so: fgetc@@GLIBC_2.0 [0xD5857] in silc_client_packet_parse_type [0xD57B0] +... +</pre></td></tr> +</table> +<p> +A TEXTREL on glibc's fgetc() function!? Either people are calling fgetc() from +assembler (and should be shot), or something else is going on. A good rule of +thumb is that if it seems like just about every function/variable reference +causes a TEXTREL and it is all done in C code, then the file was not built as +PIC. Just review the build output and see if the command to compile it was +invoked with -fPIC. If not, go fix the build system as you do not need to dig +into the source. Dodged the bullet this time! +</p> +<p class="chaphead"><a name="doc_chap3"></a><span class="chapnum">3. + </span>Dissecting broken object code</p> +<p> +So we have identified some broken libraries, and we want to fix them. The +trouble is, shared library code can be huge. They can have thousands of +functions which come from thousands of object files and thousands of source +code files which total megabytes in size (source code and compiled objects). +Where the hell do we start!? Once again, Mighty Mouse^W^W<span class="code" dir="ltr">scanelf</span> is +here to save the day. Before we dive into source code, lets check out a few +libraries. +</p> +<p class="secthead"><a name="doc_chap3_sect2">Dissect libsmpeg</a></p> +<a name="doc_chap3_pre1"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing3.1: Scan libsmpeg</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[The output has been truncated from about 35 lines]</span> +$ <span class="code-input">scanelf -qT /usr/lib/libsmpeg-0.4.so.0.1.3</span> + libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB3C] in cpu_flags [0x2FB10] + libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB42] in cpu_flags [0x2FB10] + libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB55] in IDCT_mmx [0x2FB48] + libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB84] in IDCT_mmx [0x2FB48] + /usr/lib/libsmpeg-0.4.so.0.1.3 +</pre></td></tr> +</table> +<p> +The output here tells us that the <span class="emphasis">cpu_flags</span> and the <span class="emphasis">IDCT_mmx</span> +functions are to blame for our TEXTRELs. The first field indicates that this +is poor usage of memory references. Unfortunately, the symbolic name of the +memory being referenced has not been retained in the object code (probably +because the code is hand written assembly), so we need to do a little more +digging. This is where the offset addresses come in to play along with the +<span class="code" dir="ltr">objdump</span> utility from the <span class="emphasis">binutils</span> package. The first address +(e.g. 0x2FB3C) is the offset of the TEXTREL while the second address is the +offset of the function (e.g. 0x2FB10). Get used to this because the behavior +of not retaining the symbol name is quite common. +</p> +<a name="doc_chap3_pre2"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing3.2: Dissecting libsmpeg</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">objdump -d /usr/lib/libsmpeg-0.4.so.0.1.3</span> +... + 2fb0f: 90 nop + +<span class="code-input">0002fb10 <cpu_flags>:</span> + 2fb10: 9c pushf + 2fb11: 58 pop %eax +... + 2fb32: 60 pusha + 2fb33: b8 01 00 00 00 mov $0x1,%eax + 2fb38: 0f a2 cpuid + <span class="code-input">2fb3a: 89 15 d0 d3 03 00 mov %edx,0x3d3d0</span> + 2fb40: 61 popa + <span class="code-input">2fb41: a1 d0 d3 03 00 mov 0x3d3d0,%eax</span> + 2fb46: c3 ret + 2fb47: 90 nop +... +</pre></td></tr> +</table> +<p> +As you can see here, the two lines picked out in the body of <span class="emphasis">cpu_flags</span> +have absolute memory references. In this case, they both refer to memory +location <span class="emphasis">0x3d3d0</span>. Since this object code may be loaded into any +address, using an aboslute reference obviously won't fly. That means +everytime libsmpeg is loaded into memory, the dynamic loader has to rewrite +the <span class="emphasis">0x3d3d0</span> to the actual calculated address on the fly. +</p> +<p class="secthead"><a name="doc_chap3_sect3">Dissect libdv</a></p> +<a name="doc_chap3_pre3"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing3.3: Scan libdv</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[The output has been truncated from about 180 lines]</span> +$ <span class="code-input">scanelf -qT /usr/lib/libdv.so.4.0.2</span> + libdv.so.4.0.2: (memory/fake?) [0x14AA9] in dv_parse_ac_coeffs_pass0 [0x14A84] + libdv.so.4.0.2: (memory/fake?) [0x14C28] in dv_parse_ac_coeffs_pass0 [0x14A84] + libdv.so.4.0.2: (memory/fake?) [0x14C8A] in dv_parse_video_segment [0x14C6F] + libdv.so.4.0.2: (memory/fake?) [0x14CA6] in dv_parse_video_segment [0x14C6F] + libdv.so.4.0.2: (memory/fake?) [0x15248] in _dv_idct_block_mmx [0x15210] + libdv.so.4.0.2: (memory/fake?) [0x152BE] in _dv_idct_block_mmx [0x15210] + libdv.so.4.0.2: (memory/fake?) [0x1583D] in _dv_dct_88_block_mmx [0x157F8] + libdv.so.4.0.2: (memory/fake?) [0x15847] in _dv_dct_88_block_mmx [0x157F8] + libdv.so.4.0.2: (memory/fake?) [0x15F91] in _dv_dct_248_block_mmx [0x15F58] + libdv.so.4.0.2: (memory/fake?) [0x15FE6] in _dv_dct_248_block_mmx [0x15F58] + libdv.so.4.0.2: (memory/fake?) [0x163D3] in _dv_rgbtoycb_mmx [0x163C8] + libdv.so.4.0.2: (memory/fake?) [0x163DD] in _dv_rgbtoycb_mmx [0x163C8] + libdv.so.4.0.2: dv_vlc_class_index_mask [0x149A7] in dv_decode_vlc [0x14998] + libdv.so.4.0.2: dv_vlc_class_index_rshift [0x149B0] in dv_decode_vlc [0x14998] + libdv.so.4.0.2: dv_vlc_classes [0x149B9] in dv_decode_vlc [0x14998] + libdv.so.4.0.2: dv_vlc_index_mask [0x149C4] in dv_decode_vlc [0x14998] + libdv.so.4.0.2: sign_mask [0x149EB] in dv_decode_vlc [0x14998] + libdv.so.4.0.2: sign_mask [0x14A5D] in __dv_decode_vlc [0x14A1C] + libdv.so.4.0.2: sign_mask [0x14B82] in dv_parse_ac_coeffs_pass0 [0x14A84] + libdv.so.4.0.2: dv_vlc_class_lookup5 [0x14A2F] in __dv_decode_vlc [0x14A1C] + libdv.so.4.0.2: dv_parse_ac_coeffs_pass0 [0x14E03] in dv_parse_video_segment [0x14C6F] + libdv.so.4.0.2: dv_parse_ac_coeffs [0x14E51] in dv_parse_video_segment [0x14C6F] + libdv.so.4.0.2: dv_quant_offset [0x14E69] in _dv_quant_88_inverse_x86 [0x14E5C] + libdv.so.4.0.2: dv_quant_offset [0x14FB3] in _dv_quant_x86 [0x14FA4] + /usr/lib/libdv.so.4.0.2 +</pre></td></tr> +</table> +<p> +Again, we can see that many functions (like <span class="emphasis">dv_parse_ac_coeffs_pass0</span> +and <span class="emphasis">_dv_idct_block_mmx</span>) have absolute memory references. What we also +see is that a bunch of functions which refer to variables. For example, +<span class="emphasis">dv_decode_vlc</span> misuses the variable <span class="emphasis">dv_vlc_class_index_mask</span> while +<span class="emphasis">dv_parse_video_segment</span> misuses the variable <span class="emphasis">dv_parse_ac_coeffs</span>. +Much easier to locate the problem in the source code when you have the symbol +name. +</p> +<p class="secthead"><a name="doc_chap3_sect4">Dissect libSDL</a></p> +<a name="doc_chap3_pre4"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing3.4: Scan libSDL</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[The output has been truncated from about 50 lines]</span> +$ <span class="code-input">scanelf -qT /usr/lib/libSDL-1.2.so.0.7.2</span> + libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E213] in _ConvertMMXpII32_24RGB888 [0x4E210] + libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E29E] in _ConvertMMXpII32_16RGB565 [0x4E29B] + libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E3F6] in _ConvertMMXpII32_16BGR555 [0x4E3F3] + libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E402] in _ConvertMMXpII32_16RGB555 [0x4E3FF] + libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E555] in _Hermes_X86_CPU [0x4E529] + libSDL-1.2.so.0.7.2: _copy_row [0x316A2] in SDL_SoftStretch [0x313C0] + libSDL-1.2.so.0.7.2: _mmxreturn [0x4E4FB] in _ConvertMMXpII32_16RGB555 [0x4E3FF] + libSDL-1.2.so.0.7.2: _x86return [0x4E590] in _ConvertX86p16_16BGR565 [0x4E560] + libSDL-1.2.so.0.7.2: _x86return [0x4EE99] in _ConvertX86p32_16BGR555 [0x4EDCA] + libSDL-1.2.so.0.7.2: _x86return [0x4EF4D] in _ConvertX86p32_8RGB332 [0x4EE9D] + /usr/lib/libSDL-1.2.so.0.7.2 +</pre></td></tr> +</table> +<p> +Doesn't seem to be anything new here. Poor memory usage in functions like +<span class="emphasis">_ConvertMMXpII32_24RGB888</span> and no symbol name which means it's probably +pure hand written assembler. The <span class="emphasis">SDL_SoftStretch</span> function misuses the +symbol <span class="emphasis">_copy_row</span> and since the symbol name has been retained, it's +probably inline assembly code. +</p> +<p class="chaphead"><a name="doc_chap4"></a><span class="chapnum">4. + </span>Finding the broken source code</p> +<p> +We've identified the functions and sometimes the variables which are causing +us such headaches. Before we can actually fix them though, we have to narrow +down the source code to the offending lines. Since we know the function +names and either the symbol name or a relative position in the function, we +should be able to focus our efforts quite easily. +</p> +<p class="secthead"><a name="doc_chap4_sect2">libsmpeg source dive</a></p> +<p> +Let's start with libsmpeg. We know that both the <span class="emphasis">cpu_flags</span> and +<span class="emphasis">IDCT_mmx</span> functions are broken. But where are they defined? +</p> +<a name="doc_chap4_pre1"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.1: Searching libsmpeg source</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">tar zxf smpeg-0.4.4.tar.gz</span> +$ <span class="code-input">cd smpeg-0.4.4.tar.gz</span> + +<span class="code-comment">[Find the cpu_flags function]</span> +$ <span class="code-input">grep -Rl cpu_flags *</span> +video/mmxflags_asm.S +video/parseblock.cpp +$ <span class="code-input">grep cpu_flags video/mmxflags_asm.S</span> +.globl cpu_flags + .type cpu_flags,@function <span class="code-comment"><-- here is what we want</span> +cpu_flags: + jz cpu_flags.L1 # Processor is 386 + je cpu_flags.L1 +cpu_flags.L1: + .size cpu_flags,.Lfe1-cpu_flags + +<span class="code-comment">[Find the IDCT_mmx function]</span> +$ <span class="code-input">grep -Rl IDCT_mmx *</span> +video/parseblock.cpp +video/mmxidct_asm.S +$ <span class="code-input">grep IDCT_mmx video/mmxidct_asm.S</span> +.globl IDCT_mmx + .type IDCT_mmx,@function <span class="code-comment"><-- here is what we want</span> +IDCT_mmx: + .size IDCT_mmx,.Lfe1-IDCT_mmx +</pre></td></tr> +</table> +<p> +As we suspected, both the <span class="emphasis">cpu_flags</span> and the <span class="emphasis">IDCT_mmx</span> functions +are written in pure assembly code. This makes tracking down the unamed +memory reference easier because the source code should closely match the +output of <span class="code" dir="ltr">objdump</span>. If we review the output from earlier, we know the +<span class="emphasis">cpuid</span> instruction is used. Since it isn't a common instruction, we +search for it in the source code. +</p> +<a name="doc_chap4_pre2"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.2: Find cpuid in cpu_flags</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">grep -A 8 cpuid video/mmxflags_asm.S</span> + cpuid + + movl %edx,flags + + popa + + movl flags,%eax + +cpu_flags.L1: +</pre></td></tr> +</table> +<p> +In GNU assembler, registers are prefixed with a <span class="emphasis">%</span> and constants are +prefixed with a <span class="emphasis">$</span>, that <span class="emphasis">flags</span> looks suspicious. It also lines +up well with the <span class="code" dir="ltr">objdump</span> output from earlier. So what is <span class="emphasis">flags</span>? +</p> +<a name="doc_chap4_pre3"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.3: What is 'flags'?</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +$ <span class="code-input">grep -C 2 flags video/mmxflags_asm.S</span> +.data + .align 16 + .type flags,@object +flags: .long 0 + +.text +</pre></td></tr> +</table> +<p> +Seems <span class="emphasis">flags</span> is a data variable local to <span class="path" dir="ltr">mmxflags_asm.S</span> +which functions access with absolute memory references rather than relative. +Now we are pretty much done. That's all there is to it. We started with the +library <span class="path" dir="ltr">libsmpeg.so</span> and tracked it back to the function +<span class="emphasis">cpu_flags</span> and the variable <span class="emphasis">flags</span> in the +<span class="path" dir="ltr">video/mmxflags_asm.S</span> file. That wasn't so hard now was it? :) +</p> +<p> +If we analyze the <span class="emphasis">IDCT_mmx</span> function, we find a similar trend. +</p> +<a name="doc_chap4_pre4"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.4: IDCT_mmx snippets</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[Local variables]</span> +.data + .align 8 + .type x4546454645464546,@object + .size x4546454645464546,8 +<span class="code-input">x4546454645464546</span>: + .long 0x45464546,0x45464546 + + .align 8 + .type x61f861f861f861f8,@object + .size x61f861f861f861f8,8 +<span class="code-input">x61f861f861f861f8</span>: + .long 0x61f861f8,0x61f861f8 + + .align 8 + .type scratch1,@object + .size scratch1,8 +<span class="code-input">scratch1</span>: + .long 0,0 + +<span class="code-comment">[Absolute memory references]</span> +.text +... + psraw $1, %mm5 /* t90=t93 */ + pmulhw <span class="code-input">x4546454645464546</span>,%mm0 /* V35 */ + psraw $1, %mm2 /* t97 */ +... + psubsw %mm2, %mm1 /* V32 ; free mm2 */ + pmulhw <span class="code-input">x61f861f861f861f8</span>,%mm1 /* V36 */ + psllw $1, %mm7 /* t107 */ +... + movq 8*3(%esi), %mm7 + psraw $4, %mm4 + movq %mm2, <span class="code-input">scratch1</span> /* out1 */ +</pre></td></tr> +</table> +<p class="secthead"><a name="doc_chap4_sect3">libSDL source dive</a></p> +<p> +Again, before we jump into how to fix these, lets analyze a few more source +files to get a better handle on identifying problematic code. +</p> +<a name="doc_chap4_pre5"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.5: Broken _ConvertMMXpII32_24RGB888 in libSDL code</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[objdump of _ConvertMMXpII32_24RGB888 memory reference]</span> +<span class="code-input">0004e210 <_ConvertMMXpII32_24RGB888>:</span> + <span class="code-input">4e210: 0f 6f 35 50 55 05 00 movq 0x55550,%mm6</span> + 4e217: 0f ef ff pxor %mm7,%mm7 + +<span class="code-comment">[_ConvertMMXpII32_24RGB888 is defined in src/hermes/mmxp2_32.asm]</span> + SECTION .data +ALIGN 8 +;; Constants for conversion routines +mmx32_rgb888_mask dd 00ffffffh,00ffffffh +... + SECTION .text +_ConvertMMXpII32_24RGB888: <span class="code-comment">start of function 0x4E210</span> + ; set up mm6 as the mask, mm7 as zero + movq mm6, qword [mmx32_rgb888_mask] <span class="code-comment">memory reference 0x4E213</span> + pxor mm7, mm7 +</pre></td></tr> +</table> +<p> +Simple enough, the <span class="emphasis">_ConvertMMXpII32_24RGB888</span> function refers to the +<span class="emphasis">mmx32_rgb888_mask</span> variable. +</p> +<a name="doc_chap4_pre6"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing4.6: Broken SDL_SoftStretch in libSDL code</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[SDL_SoftStretch is defined in src/video/SDL_stretch.c]</span> +int SDL_SoftStretch(SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect) +{ +... +#ifdef __GNUC__ + __asm__ __volatile__ ( + "call _copy_row" + : "=&D" (u1), "=&S" (u2) + : "0" (dstp), "1" (srcp) + : "memory" ); +#else +</pre></td></tr> +</table> +<p> +Another straight forward bug. An absolute reference to the <span class="emphasis">_copy_row</span> +variable in assembly. If we were to let gcc handle the <span class="emphasis">_copy_row</span> +reference instead though ... +</p> +<p class="chaphead"><a name="doc_chap5"></a><span class="chapnum">5. + </span>How to write PIC (in theory)</p> +<p class="secthead"><a name="doc_chap5_sect1">Rules of thumb</a></p> +<p> +Now we know what broken code looks like. We can point out issues in code and +confidently declare "that crap is broken". While this is a good thing, it +certainly doesn't help much if no one knows how it's supposed to be written. +Let's start with some rules of thumb. +</p> +<p>General rules</p> +<ul> + <li>Do not mix PIC and non-PIC object code</li> + <li>Shared libraries contain PIC objects</li> + <li>Static libraries contain non-PIC objects (normal/non-PIE systems only)</li> + <li>Let gcc figure out the details whenever possible (e.g. inline asm)</li> + <li>Use the stack for loading of large masks instead of variables</li> + <li>Do not clobber the PIC register when generating PIC objects</li> +</ul> +<p>x86-specific rules</p> +<ul> + <li>Use @GOT relocations when using external symbols</li> + <li>Use @GOTOFF relocations when using local symbols</li> +</ul> +<p class="secthead"><a name="doc_chap5_sect2">PIC registers by architecture</a></p> +<table class="ntable"> + <tr> +<td class="infohead"><b>arch</b></td> +<td class="infohead"><b>register</b></td> +</tr> + <tr> +<td class="tableinfo">blackfin</td> +<td class="tableinfo">P3</td> +</tr> + <tr> +<td class="tableinfo">frv</td> +<td class="tableinfo">GR15</td> +</tr> + <tr> +<td class="tableinfo">hppa</td> +<td class="tableinfo">r19</td> +</tr> + <tr> +<td class="tableinfo">x86</td> +<td class="tableinfo">ebx</td> +</tr> +</table> +<p class="chaphead"><a name="doc_chap6"></a><span class="chapnum">6. + </span>Cookie cutter PIC fixes</p> +<p class="secthead"><a name="doc_chap6_sect1">Don't use the PIC register</a></p> +<p> +If you come across code which uses the PIC register in some inline assembly, +one fix may be to simply use a different register. For example, the x86 +architecture has 6 general purpose registers (<span class="emphasis">eax</span>, <span class="emphasis">ebx</span>, +<span class="emphasis">ecx</span>, <span class="emphasis">edx</span>, <span class="emphasis">esi</span>, <span class="emphasis">edi</span>). If the code uses just +<span class="emphasis">eax</span> and <span class="emphasis">ebx</span>, just change all references to <span class="emphasis">ebx</span> to +<span class="emphasis">ecx</span> and you're done! +</p> +<p> +A cleaner fix might be to just let gcc allocate the registers accordingly. If +the inline assembly doesn't actually care which registers it uses, change the +references from <span class="emphasis">ebx</span> to <span class="emphasis">r</span> in the clobber list, and refer to the +variable by number. +</p> +<p> +Or, if the assembly uses an instruction which always clobbers <span class="emphasis">ebx</span> (e.g. +<span class="emphasis">cpuid</span>), simply hide the value in another register (like <span class="emphasis">esi</span>). +</p> +<p> +If all else fails, you can fall back to the slow push/pop <span class="emphasis">ebx</span> on the +stack method. +</p> +<a name="doc_chap6_pre1"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.1: Just don't use the PIC register</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">/* change this code from */</span> +asm(" + mov %0, %%eax + mov %1, %%ebx + add %%eax, %%ebx + " : : "m"(input1), "m"(input2) : "eax" "ebx"); + +<span class="code-comment">/* to this functionality equivalent version */</span> +asm(" + mov %0, %%eax + mov %1, %%ecx + add %%eax, %%ecx + " : : "m"(input1), "m"(input2) : "eax" "ecx"); +</pre></td></tr> +</table> +<a name="doc_chap6_pre2"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.2: Let gcc allocate registers</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">/* change this code from */</span> +asm(" + mov %2, %%eax + mov %3, %%ebx + add %%eax, %%ebx + " : "=a"(output1) "=b"(output2) : "m"(input1), "m"(input2)); + +<span class="code-comment">/* to this functionality equivalent version */</span> +asm(" + mov %2, %0 + mov %3, %1 + add %0, %1 + " : "=r"(output1) "=r"(output2) : "m"(input1), "m"(input2)); +</pre></td></tr> +</table> +<a name="doc_chap6_pre3"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.3: Hide the PIC register</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +asm("cpuid" : : : "eax", "ebx", "ecx", "edx"); + +<span class="code-comment">/* can be written to hide ebx */</span> +asm(" + movl %%ebx, %%edi + cpuid + movl %%edi, %%ebx + " : : : "eax", "ecx", "edx", "edi"); + +<span class="code-comment">/* or a slower version using the stack */</span> +asm(" + pushl %%ebx + cpuid + popl %%ebx + " : : : "eax", "ecx", "edx"); +</pre></td></tr> +</table> +<p class="secthead"><a name="doc_chap6_sect2">MMX/SSE masks</a></p> +<p> +A lot of x86 MMX/SSE code loads bitmasks from local variables since they need +to fill up a register which is larger (MMX/64bits or SSE/128bits) than the +native bitsize (x86/32bits). They do this by defining the mask in +consecutive bytes in memory and then having the cpu load the data from the +memory region. +</p> +<p> +One way to get around this is by being creative with the stack. Rather than +use an absolute memory reference for the mask, push a bunch of 32bit values +onto the stack and use the address specified by the <span class="emphasis">esp</span> register. +Once you're finished, just add a constant to <span class="emphasis">esp</span> rather than popping +off since you don't care about the actual values once they are loaded into +the MMX/SSE registers. +</p> +<a name="doc_chap6_pre4"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.4: Load masks into registers via stack</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">/* Load masks from memory (causes TEXTRELs) */</span> + .data +m0X000000: .byte 0, 0, 0, 0, 0, 0, 255, 0 + .text +movq m0X000000, %mm5 + +<span class="code-comment">/* Load mask from stack (no TEXTRELs)*/</span> +pushl $0x00FF0000 +pushl $0x00000000 +movq (%esp), %mm5 +addl 8, %esp +</pre></td></tr> +</table> +<p class="secthead"><a name="doc_chap6_sect3">Let gcc worry about it</a></p> +<p> +A lot of inline assembly is written with the symbol names placed right in the +code. Rather than trying to write custom code to handle PIC in assembly, just +let gcc worry about it. Pass in the symbol via the input operand list as a +memory constraint ("m") and gcc will handle all the rest. +</p> +<a name="doc_chap6_pre5"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.5: How to make gcc worry about it</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +unsigned long long a_mmx_mask = 0xf8007c00ffea0059ULL; +void somefunction() +{ + <span class="code-comment">/* Common (but incorrect) method for loading masks */</span> + asm("pmullw a_mmx_mask, %%mm0" : : ); + + <span class="code-comment">/* The correct way is to let gcc do it */</span> + asm("pmullw %0, %%mm0" : : "m"(a_mmx_mask)); +} +</pre></td></tr> +</table> +<p> +If your get a warning/error about one of the memory inputs needing to be an +lvalue, then this usually means you're trying to pass in a pointer to an +array/structure rather than the memory location itself. Fixing this may be as +simple as dereferencing the variable in the constraint list rather than in the +assembly itself. +</p> +<p class="secthead"><a name="doc_chap6_sect4">Thunk it in assembly</a></p> +<p> +Hand written assembly sometimes need to access variables (whether they be +local or global). Since none of the previous tricks will work, you just need +to grind your teeth and dig in to write real PIC references yourself using +the GOT. Make sure you keep in mind the first rule of thumb: Do not mix PIC +and non-PIC object code. This probably will require the hand written +assembly be preprocessed before it is assembled, so an assembly source file +with a <span class="emphasis">.s</span> suffix will not work. It needs to be <span class="emphasis">.S</span>. +</p> +<p> +Also keep in mind that using @GOTOFF will return the variable while using @GOT +will return a pointer to the variable. So accessing a variable with @GOT will +require two steps. +</p> +<a name="doc_chap6_pre6"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing6.6: How to refer to variables via the GOT</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +#ifdef __PIC__ + +# undef __i686 /* gcc builtin define gets in our way */ +# define MUNG_LOCAL(sym) sym ## @GOTOFF(%ebx) +# define MUNG_EXTERN(sym) sym ## @GOT(%ebx) +# define DEREF_EXTERN(reg) movl (reg), reg +# define INIT_PIC() \ + call __i686.get_pc_thunk.bx ; \ + addl $_GLOBAL_OFFSET_TABLE_, %ebx + +#else + +# define MUNG_LOCAL(sym) sym +# define MUNG_EXTERN(sym) sym +# define DEREF_EXTERN(reg) +# define INIT_PIC() + +#endif + +... +some_function: +... + <span class="code-comment">/* needs to be before first memory reference */</span> + INIT_PIC() +... + movl MUNG_EXTERN(some_external_variable), %eax + DEREF_EXTERN(%eax) +... + movl %eax, MUNG_LOCAL(some_local_variable) +... + +#ifdef __PIC__ + .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits +.globl __i686.get_pc_thunk.bx + .hidden __i686.get_pc_thunk.bx + .type __i686.get_pc_thunk.bx,@function +__i686.get_pc_thunk.bx: + movl (%esp), %ebx + ret +#endif +</pre></td></tr> +</table> +<table class="ncontent" width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td bgcolor="#bbffbb"><p class="note"><b>Note: </b> +Usage of <span class="emphasis">ebx</span> as the register to do relative addressing is not required, +it is just common convention. The above examples could just as easily be done +by using <span class="emphasis">ecx</span> or <span class="emphasis">edx</span>. +</p></td></tr></table> +<p> +Since we hide the PIC details behind the preprocessor define <span class="emphasis">__PIC__</span>, +we know that the correct code will be generated for both the PIC and non-PIC +cases. +</p> +<p> +The <span class="emphasis">__i686.get_pc_thunk.bx</span> function is a standard method for acquiring +the address of the GOT at runtime and storing the result in <span class="emphasis">ebx</span>. The +funky name is what gcc uses by convention when generating PIC objects, so we +too use the same name. The <span class="emphasis">@GOT</span> and <span class="emphasis">@GOTOFF</span> notation tells the +assembler where to find the variables in memory. The <span class="emphasis">.section +.gnu.linkonce.t</span> is useful because it tells the linker to only include one +instance of this function in the final object code. So if you have multiple +files which declare this same function which are compiled and linked into the +same final library, the linker will discard all duplicate instances of the +function thus saving space (which is always a good thing). +</p> +<p class="chaphead"><a name="doc_chap7"></a><span class="chapnum">7. + </span>How to fix broken PIC (in practice)</p> +<p> +So if the previous code snippets were broken, what should they look like you +may wonder. Well let's find out. +</p> +<p class="secthead"><a name="doc_chap7_sect2">Fix libsmpeg</a></p> +<a name="doc_chap7_pre1"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing7.1: Fixing cpu_flags in libsmpeg by rewriting it</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[Non-PIC Version]</span> +.type flags,@object +flags: .long 0 +... + pusha + movl $1,%eax + cpuid + movl %edx,flags + popa + movl flags,%eax + + +<span class="code-comment">[PIC Version]</span> + pushl %ebx + movl $1,%eax + cpuid + movl %edx,%eax + popl %ebx +</pre></td></tr> +</table> +<a name="doc_chap7_pre2"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing7.2: Fixing IDCT_mmx in libsmpeg by using relative addressing</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[Non-PIC Version]</span> + pmulhw x5a825a825a825a82, %mm1 + + +<span class="code-comment">[PIC Version]</span> +#ifdef __PIC__ +# undef __i686 /* gcc define gets in our way */ + call __i686.get_pc_thunk.bx + addl $_GLOBAL_OFFSET_TABLE_, %ebx +#endif +... + pmulhw x5a825a825a825a82@GOTOFF(%ebx), %mm1 +... +#ifdef __PIC__ + .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits +.globl __i686.get_pc_thunk.bx + .hidden __i686.get_pc_thunk.bx + .type __i686.get_pc_thunk.bx,@function +__i686.get_pc_thunk.bx: + movl (%esp), %ebx + ret +#endif +</pre></td></tr> +</table> +<p class="secthead"><a name="doc_chap7_sect3">Fix libSDL</a></p> +<a name="doc_chap7_pre3"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing7.3: Fixing _ConvertMMXpII32_24RGB888 in libSDL</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[Non-PIC Version]</span> +mmx32_rgb888_mask dd 00ffffffh,00ffffffh +... + movq mm6, qword [mmx32_rgb888_mask] + + +<span class="code-comment">[PIC Version]</span> +%macro _push_immq_mask 1 + push dword %1 + push dword %1 +%endmacro +%macro load_immq 2 + _push_immq_mask %2 + movq %1, [esp] +%endmacro +%define mmx32_rgb888_mask 00ffffffh +... + load_immq mm6, mmx32_rgb888_mask + CLEANUP_IMMQ_LOADS(1) +</pre></td></tr> +</table> +<a name="doc_chap7_pre4"></a><table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"> +<tr><td bgcolor="#7a5ada"><p class="codetitle">Code Listing7.4: Fixing SDL_SoftStretch in libSDL</p></td></tr> +<tr><td bgcolor="#eeeeff" align="left" dir="ltr"><pre> +<span class="code-comment">[Non-PIC Version]</span> + __asm__ __volatile__ ( + "call _copy_row" + : "=&D" (u1), "=&S" (u2) + : "0" (dstp), "1" (srcp) + : "memory" ); + + +<span class="code-comment">[PIC Version]</span> + __asm__ __volatile__ ( + "call *%4" + : "=&D" (u1), "=&S" (u2) + : "0" (dstp), "1" (srcp), "r" (&_copy_row) + : "memory" ); +</pre></td></tr> +</table> +<p class="chaphead"><a name="doc_chap8"></a><span class="chapnum">8. + </span>References</p> +<ul> + <li>thanks to the PaX team for holding my hand</li> + <li><a href="http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html">GCC Inline Assembly HOWTO</a></li> + <li> +<a href="http://nasm.sourceforge.net/">NASM</a>'s Documentation on <a href="http://nasm.sourceforge.net/doc/html/nasmdoc6.html#section-6.5.2">ELF shared libraries</a> +</li> + <li>Linkers and Loaders <a href="http://www.iecc.com/linker/linker08.html">chapter 8</a> and <a href="http://www.iecc.com/linker/linker10.html">chapter 10</a> +</li> +</ul> +<br><br> +</td> +<td width="1%" bgcolor="#dddaec" valign="top"><table border="0" cellspacing="4px" cellpadding="4px"> +<tr><td class="topsep" align="center"><p class="altmenu"><a title="View a printer-friendly version" class="altlink" href="http://www.gentoo.org/proj/en/hardened/pic-fix-guide.xml?style=printable">Print</a></p></td></tr> +<tr><td class="topsep" align="center"><p class="alttext">Updated August 19, 2007</p></td></tr> +<tr><td class="topsep" align="left"><p class="alttext"><b>Summary: </b>A guide for tracking down and fixing .text relocations (TEXTRELs)</p></td></tr> +<tr><td align="left" class="topsep"><p class="alttext"> + <a href="mailto:vapier@gentoo.org" class="altlink"><b>Mike Frysinger</b></a> +<br><i>Author</i><br><br> + <a href="mailto:solar@gentoo.org" class="altlink"><b>solar</b></a> +<br><i>Author</i><br><br> + <a href="mailto:pageexec@freemail.hu" class="altlink"><b>The PaX team</b></a> +<br><i>Contributor</i><br><br> + <a href="mailto:kevquinn@gentoo.org" class="altlink"><b>Kevin F. Quinn</b></a> +<br><i>Contributor</i><br></p></td></tr> +<tr lang="en"><td align="center" class="topsep"> +<p class="alttext"><b>Donate</b> to support our development efforts. + </p> +<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> +<input type="hidden" name="cmd" value="_xclick"><input type="hidden" name="business" value="paypal@gentoo.org"><input type="hidden" name="item_name" value="Gentoo Linux Support"><input type="hidden" name="item_number" value="1000"><input type="hidden" name="image_url" value="http://www.gentoo.org/images/paypal.png"><input type="hidden" name="no_shipping" value="1"><input type="hidden" name="return" value="http://www.gentoo.org"><input type="hidden" name="cancel_return" value="http://www.gentoo.org"><input type="image" src="http://images.paypal.com/images/x-click-but21.gif" name="submit" alt="Donate to Gentoo"> +</form> +</td></tr> +<tr lang="en"><td align="center"><iframe src="http://sidebar.gentoo.org" scrolling="no" width="125" height="850" frameborder="0" style="border:0px padding:0x" marginwidth="0" marginheight="0"><p>Your browser does not support iframes.</p></iframe></td></tr> +</table></td> +</tr></table></td></tr> +<tr><td colspan="2" align="right" class="infohead"> +Copyright 2001-2010 Gentoo Foundation, Inc. Questions, Comments? <a class="highlight" href="http://www.gentoo.org/main/en/contact.xml">Contact us</a>. +</td></tr> +</table></body> +</html> |