📄 nasmdoc8.htm
字号:
<h3><a name="section-8.2">8.2 Writing NetBSD/FreeBSD/OpenBSD and Linux/ELF Shared Libraries</a></h3><p><code><nobr>ELF</nobr></code> replaced the older<code><nobr>a.out</nobr></code> object file format under Linux because itcontains support for position-independent code (PIC), which makes writingshared libraries much easier. NASM supports the<code><nobr>ELF</nobr></code> position-independent code features, so youcan write Linux <code><nobr>ELF</nobr></code> shared libraries in NASM.<p>NetBSD, and its close cousins FreeBSD and OpenBSD, take a differentapproach by hacking PIC support into the <code><nobr>a.out</nobr></code>format. NASM supports this as the <code><nobr>aoutb</nobr></code> outputformat, so you can write BSD shared libraries in NASM too.<p>The operating system loads a PIC shared library by memory-mapping thelibrary file at an arbitrarily chosen point in the address space of therunning process. The contents of the library's code section must thereforenot depend on where it is loaded in memory.<p>Therefore, you cannot get at your variables by writing code like this:<p><pre> mov eax,[myvar] ; WRONG</pre><p>Instead, the linker provides an area of memory called the <em>globaloffset table</em>, or GOT; the GOT is situated at a constant distance fromyour library's code, so if you can find out where your library is loaded(which is typically done using a <code><nobr>CALL</nobr></code> and<code><nobr>POP</nobr></code> combination), you can obtain the address ofthe GOT, and you can then load the addresses of your variables out oflinker-generated entries in the GOT.<p>The <em>data</em> section of a PIC shared library does not have theserestrictions: since the data section is writable, it has to be copied intomemory anyway rather than just paged in from the library file, so as longas it's being copied it can be relocated too. So you can put ordinary typesof relocation in the data section without too much worry (but see<a href="#section-8.2.4">section 8.2.4</a> for a caveat).<h4><a name="section-8.2.1">8.2.1 Obtaining the Address of the GOT</a></h4><p>Each code module in your shared library should define the GOT as anexternal symbol:<p><pre>extern _GLOBAL_OFFSET_TABLE_ ; in ELF extern __GLOBAL_OFFSET_TABLE_ ; in BSD a.out</pre><p>At the beginning of any function in your shared library which plans toaccess your data or BSS sections, you must first calculate the address ofthe GOT. This is typically done by writing the function in this form:<p><pre>func: push ebp mov ebp,esp push ebx call .get_GOT .get_GOT: pop ebx add ebx,_GLOBAL_OFFSET_TABLE_+$$-.get_GOT wrt ..gotpc ; the function body comes here mov ebx,[ebp-4] mov esp,ebp pop ebp ret</pre><p>(For BSD, again, the symbol<code><nobr>_GLOBAL_OFFSET_TABLE</nobr></code> requires a second leadingunderscore.)<p>The first two lines of this function are simply the standard C prologueto set up a stack frame, and the last three lines are standard C functionepilogue. The third line, and the fourth to last line, save and restore the<code><nobr>EBX</nobr></code> register, because PIC shared libraries usethis register to store the address of the GOT.<p>The interesting bit is the <code><nobr>CALL</nobr></code> instructionand the following two lines. The <code><nobr>CALL</nobr></code> and<code><nobr>POP</nobr></code> combination obtains the address of the label<code><nobr>.get_GOT</nobr></code>, without having to know in advance wherethe program was loaded (since the <code><nobr>CALL</nobr></code>instruction is encoded relative to the current position). The<code><nobr>ADD</nobr></code> instruction makes use of one of the specialPIC relocation types: GOTPC relocation. With the<code><nobr>WRT ..gotpc</nobr></code> qualifier specified, the symbolreferenced (here <code><nobr>_GLOBAL_OFFSET_TABLE_</nobr></code>, thespecial symbol assigned to the GOT) is given as an offset from thebeginning of the section. (Actually, <code><nobr>ELF</nobr></code> encodesit as the offset from the operand field of the<code><nobr>ADD</nobr></code> instruction, but NASM simplifies thisdeliberately, so you do things the same way for both<code><nobr>ELF</nobr></code> and <code><nobr>BSD</nobr></code>.) So theinstruction then <em>adds</em> the beginning of the section, to get thereal address of the GOT, and subtracts the value of<code><nobr>.get_GOT</nobr></code> which it knows is in<code><nobr>EBX</nobr></code>. Therefore, by the time that instruction hasfinished, <code><nobr>EBX</nobr></code> contains the address of the GOT.<p>If you didn't follow that, don't worry: it's never necessary to obtainthe address of the GOT by any other means, so you can put those threeinstructions into a macro and safely ignore them:<p><pre>%macro get_GOT 0 call %%getgot %%getgot: pop ebx add ebx,_GLOBAL_OFFSET_TABLE_+$$-%%getgot wrt ..gotpc %endmacro</pre><h4><a name="section-8.2.2">8.2.2 Finding Your Local Data Items</a></h4><p>Having got the GOT, you can then use it to obtain the addresses of yourdata items. Most variables will reside in the sections you have declared;they can be accessed using the <code><nobr>..gotoff</nobr></code> special<code><nobr>WRT</nobr></code> type. The way this works is like this:<p><pre> lea eax,[ebx+myvar wrt ..gotoff]</pre><p>The expression <code><nobr>myvar wrt ..gotoff</nobr></code> iscalculated, when the shared library is linked, to be the offset to thelocal variable <code><nobr>myvar</nobr></code> from the beginning of theGOT. Therefore, adding it to <code><nobr>EBX</nobr></code> as above willplace the real address of <code><nobr>myvar</nobr></code> in<code><nobr>EAX</nobr></code>.<p>If you declare variables as <code><nobr>GLOBAL</nobr></code> withoutspecifying a size for them, they are shared between code modules in thelibrary, but do not get exported from the library to the program thatloaded it. They will still be in your ordinary data and BSS sections, soyou can access them in the same way as local variables, using the above<code><nobr>..gotoff</nobr></code> mechanism.<p>Note that due to a peculiarity of the way BSD<code><nobr>a.out</nobr></code> format handles this relocation type, theremust be at least one non-local symbol in the same section as the addressyou're trying to access.<h4><a name="section-8.2.3">8.2.3 Finding External and Common Data Items</a></h4><p>If your library needs to get at an external variable (external to the<em>library</em>, not just to one of the modules within it), you must usethe <code><nobr>..got</nobr></code> type to get at it. The<code><nobr>..got</nobr></code> type, instead of giving you the offset fromthe GOT base to the variable, gives you the offset from the GOT base to aGOT <em>entry</em> containing the address of the variable. The linker willset up this GOT entry when it builds the library, and the dynamic linkerwill place the correct address in it at load time. So to obtain the addressof an external variable <code><nobr>extvar</nobr></code> in<code><nobr>EAX</nobr></code>, you would code<p><pre> mov eax,[ebx+extvar wrt ..got]</pre><p>This loads the address of <code><nobr>extvar</nobr></code> out of anentry in the GOT. The linker, when it builds the shared library, collectstogether every relocation of type <code><nobr>..got</nobr></code>, andbuilds the GOT so as to ensure it has every necessary entry present.<p>Common variables must also be accessed in this way.<h4><a name="section-8.2.4">8.2.4 Exporting Symbols to the Library User</a></h4><p>If you want to export symbols to the user of the library, you have todeclare whether they are functions or data, and if they are data, you haveto give the size of the data item. This is because the dynamic linker hasto build procedure linkage table entries for any exported functions, andalso moves exported data items away from the library's data section inwhich they were declared.<p>So to export a function to users of the library, you must use<p><pre>global func:function ; declare it as a function func: push ebp ; etc.</pre><p>And to export a data item such as an array, you would have to code<p><pre>global array:data array.end-array ; give the size too array: resd 128 .end:</pre><p>Be careful: If you export a variable to the library user, by declaringit as <code><nobr>GLOBAL</nobr></code> and supplying a size, the variablewill end up living in the data section of the main program, rather than inyour library's data section, where you declared it. So you will have toaccess your own global variable with the <code><nobr>..got</nobr></code>mechanism rather than <code><nobr>..gotoff</nobr></code>, as if it wereexternal (which, effectively, it has become).<p>Equally, if you need to store the address of an exported global in oneof your data sections, you can't do it by means of the standard sort ofcode:<p><pre>dataptr: dd global_data_item ; WRONG</pre><p>NASM will interpret this code as an ordinary relocation, in which<code><nobr>global_data_item</nobr></code> is merely an offset from thebeginning of the <code><nobr>.data</nobr></code> section (or whatever); sothis reference will end up pointing at your data section instead of at theexported global which resides elsewhere.<p>Instead of the above code, then, you must write<p><pre>dataptr: dd global_data_item wrt ..sym</pre><p>which makes use of the special <code><nobr>WRT</nobr></code> type<code><nobr>..sym</nobr></code> to instruct NASM to search the symbol tablefor a particular symbol at that address, rather than just relocating bysection base.<p>Either method will work for functions: referring to one of yourfunctions by means of<p><pre>funcptr: dd my_function</pre><p>will give the user the address of the code you wrote, whereas<p><pre>funcptr: dd my_function wrt .sym</pre><p>will give the address of the procedure linkage table for the function,which is where the calling program will <em>believe</em> the functionlives. Either address is a valid way to call the function.<h4><a name="section-8.2.5">8.2.5 Calling Procedures Outside the Library</a></h4><p>Calling procedures outside your shared library has to be done by meansof a <em>procedure linkage table</em>, or PLT. The PLT is placed at a knownoffset from where the library is loaded, so the library code can make callsto the PLT in a position-independent way. Within the PLT there is code tojump to offsets contained in the GOT, so function calls to other sharedlibraries or to routines in the main program can be transparently passedoff to their real destinations.<p>To call an external routine, you must use another special PIC relocationtype, <code><nobr>WRT ..plt</nobr></code>. This is much easier than theGOT-based ones: you simply replace calls such as<code><nobr>CALL printf</nobr></code> with the PLT-relative version<code><nobr>CALL printf WRT ..plt</nobr></code>.<h4><a name="section-8.2.6">8.2.6 Generating the Library File</a></h4><p>Having written some code modules and assembled them to<code><nobr>.o</nobr></code> files, you then generate your shared librarywith a command such as<p><pre>ld -shared -o library.so module1.o module2.o # for ELF ld -Bshareable -o library.so module1.o module2.o # for BSD</pre><p>For ELF, if your shared library is going to reside in system directoriessuch as <code><nobr>/usr/lib</nobr></code> or<code><nobr>/lib</nobr></code>, it is usually worth using the<code><nobr>-soname</nobr></code> flag to the linker, to store the finallibrary file name, with a version number, into the library:<p><pre>ld -shared -soname library.so.1 -o library.so.1.2 *.o</pre><p>You would then copy <code><nobr>library.so.1.2</nobr></code> into thelibrary directory, and create <code><nobr>library.so.1</nobr></code> as asymbolic link to it.<p align=center><a href="nasmdoc9.html">Next Chapter</a> |<a href="nasmdoc7.html">Previous Chapter</a> |<a href="nasmdoc0.html">Contents</a> |<a href="nasmdoci.html">Index</a></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -