User:Eric Bachard/Port sur nouvelle architecture Linux

Ceci est une traduction de la page créée par Caolan McNamara, issue du wiki d'OpenOffice.org, pour traduction et amélioration. Elle servira de fil conducteur pour le port Amiga OS 4.1 d'OOo4Kids, et sera régulièrement complétée et modifiée au fur et à mesure de l'avancée de ce port.

Titre et lien originaux : A Quick Guide to Porting to an unsupported Linux Architecture

Nouveau titre : Port d'OOo4Kids sur une architecture Linux non encore supportée

Partie facile
1. Pour commencer, éditer set_soenv.in et ajouter une entrée pour votre plateforme avec un nom basé sur la sortie du script ./config.guess par exemple, copier ce qui a été fait pour le simple m68k et l'adapter. Faire attention a prendre des variables d'environnement CPU, CPUNAME et OUTPATH qui ne créent pas de conflit avec les ports existants. (ici, OUTPATH de unxlnghppa signifie Unix LiNux GCC sur HPPA). La section par défaut "parisc" dans les variables de type JRE devraient résulter de la sortie de uname -m, sauf si le JRE sur la plateforme est connu pour être différent.

elsif ($platform =~ m/^hppa/) { print "Setting Linux hppa specific values... "; $outfile       = "LinuxHPPAEnv.Set"; $CPU           = "H"; $CPUNAME       = "HPPA"; $OUTPATH       = "unxlnghppa"; $JRELIBDIR     = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc"; $JRETOOLKITDIR = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc".$ds."server"; $JRETHREADDIR  = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc".$ds."native_threads"; }

2. Éditer ensuite solenv/inc/unx.mk et ajouter ensuite une entrée pour votre plateforme, basée sur les variable OS et CPU que vous avez ajoutés à set_soenv.in pour qu'un makefile dédié à cette plateforme soit créé.

.IF "$(COM)$(OS)$(CPU)" == "GCCLINUXH" .INCLUDE : unxlnghppa.mk .ENDIF

3. Créer le .mk associé à la nouvelle plateforme. Pour cela, utiliser le nom défini en 2, par exemple, et en recopiant le contenu de unxlngm68k.mk dedans (en faisant les modifications appropriées, par exemple, choisir un suffixe DLL et un PICSWITCH corrects pour cette plateforme, et enfin, définir le nouveau -D$CPUNAME

PICSWITCH:=-fPIC .INCLUDE : unxlng.mk CDEFS+=-DHPPA CFLAGS+= CFLAGSCC+= CFLAGSCXX+= DLLPOSTFIX=lh
 * 1) fichier mk Unix Linux HPPA utilisant GCC, merci de faire les modifications génériques pour Linux dans unxlng.mk

4. Éditer sal/rtl/source/macro.hxx et ajouter une entrée pour cette nouvelle plateforme (utilisée pour déterminer quelle extensions peuvent être compilées dans le produit final). Cette macro doit correspondre à la directive utilisée dans 3 pour -D$CPUNAME.


 * 1) elif defined HPPA
 * 2)    define THIS_ARCH "HPPA"
 * 3) endif

5. Vous pourrez aussi avoir à éditer sal/typesconfig/typesconfig.c, car pour certaines plateformes pour lesquelles l'accès à des données non alignées est autorisée, mais non souhaité, par exemple, certains noyaux Linux sur ARM ( et toujours sous qemu je crois ?) autorisent certains acccès non-alignés de la mémoire. Cela conduit à des bidouillages du noyau, de type grossiers et inefficaces. Ceci est même interdit pour certains noyaux de type Linux ARM. ia64 est un autre type de plateforme sur laquelle il ne sera pas interdit d'avoir un accès non-aligné en mémoire, mais nous ne voulons pas cela, et allons même faire en sorte que cela n'arrive pas.


 * 1) if defined(IA64) || defined(ARM32) || defined(HPPA)

6. Éditer tools/inc/tools/solar.h et ajouter une entrée qui correspond avec le suffixe choisi en 3

#define __DLLEXTENSION "lh.so"
 * 1) elif defined LINUX && defined HPPA

7. Éditer automation/source/testtool/objtest.cxx et ajouter une entrée pour votre plateforme + architecture.

abGP.Append( "22" ); // Linux PA-RISC
 * 1) elif defined LINUX && defined HPPA
 * 1) else

8. Éditer jvmfwk/plugins/sunmajor/pluginlib/vendorbase.hxx et ajouter une entrée pour votre plateforme + architecture (cette entrée doit suivre l'espace de nommage de la jvm définie dans 1, par exemple /usr/lib/jvm/java-version/jre/lib/ARCH qui est générallement le nom retourné par uname -m

#define JFW_PLUGIN_ARCH "parisc"
 * 1) elif defined HPPA

9. Éditer desktop/source/deployment/misc/dp_platform.cxx at ajouter une entrée pour votre plateforme + architecture, en réutilisant la même information que celle utilisée dans 4

Partie difficile
9. Dans le répertoire bridges/source/cpp_uno, créez un répertoire pour votre architecture, par exemple cp -r gcc_linux_m68k to gcc_linux_hppa et éditez le makefile.mk dans ce nouveau répertoire, afin qu'il coïncide avec 1 et 2, et ensuite faites les changements nécessaires. Pour faire fonctionner votre plateforme, vous aurez peut être de la chance, et n'aurez à modifier que quelques petites choses, sauf la partie exceptions, qui est commune à tous les Linuxes sauf la partie ARM-eabi si je me souviens bien.

10. Éditez bridges/prj/build.lst et ajoutez une entrée, ce qui permettra de compiler ce répertoire. Exemple : br     bridges\source\cpp_uno\gcc3_linux_hppa          nmake   -       u       br_gccl3h br_unotypes NULL

11. codeSnippet at runtime generates the code needed to trampoline to a specified handler. the intent is that calls to virtual methods of a proxy object get routed to that handler along with all the original arguments passed to the virtual method, with the additional information as to what is the index of this virtual method and the offset of the vtable.

It varies in complexity from the trivial ppc64 one where the abi is particularly convenient to more complicated ones like the sparc one.

To take hppa as an example we start off just trying to get a trampoline to work to jump to a handler on virtual method invocation, e.g. a test.s of

b,l .+8,%r21       ; %r21 <- pc+8 */ depi 0,31,2,%r21   ; mask priv bits */ ldw 16(%r21),%r1   ; load plabel where 16 is the relative offset to where we've copied the address of the handler*/ ldw 0(%r1),%r23    ; address of handler */ bv %r0(%r23)       ; branch to handler */ ldw 4(%r1),%r19    ; GP of handler */ ldw 4(%r1),%r19    ; place address of handler here

as test.s -o test.o objdump -d test.o

Disassembly of section .text: 00000000 <.text>: 0: ea a0 00 00     b,l 0x8,r21 4: d6 a0 1c 1e     depwi 0,31,2,r21 8: 4a a1 00 20     ldw 20(r21),r1 c: 0c 20 10 97     ldw 0(r1),r23 10: ea e0 c0 00    bv r0(r23) 14: 0c 28 10 93     ldw 4(r1),r19

So whack this into codeSnippet and tweak it

*(unsigned long*)&p[0] = 0xeaa00000; /* b,l .+8,%r21        ; %r21 <- pc+8 */ *(unsigned long*)&p[4] = 0xd6a01c1e; /* depi 0,31,2,%r21    ; mask priv bits */ *(unsigned long*)&p[8] = 0x4aa10020; /* ldw 20(%r21),%r1    ; load plabel */ *(unsigned long*)&p[12] = 0x0c201097; /* ldw 0(%r1),%r23    ; address of handler */ *(unsigned long*)&p[16] = 0xeae0c000; /* bv %r0(%r23)       ; branch to handler */ *(unsigned long*)&p[20] = 0x0c281093; /* ldw 4(%r1),%r19    ; GP of handler */ *(unsigned long*)&p[24] = ((unsigned long)(cpp_vtable_call) & ~2);

make cpp_vtable_call(void) output hello world or whatever

cd testtools build

You should see that "hello world" before it falls over and breaks.

12. You'll possibly need a implementation of flushCode. Almost definitely if its some form of RISC in order to flush the icache/dcache, e.g. HPPA version is something like...

void bridges::cpp_uno::shared::VtableFactory::flushCode(   unsigned char const *beg, unsigned char const *end) {   void *p = (void*)((size_t)beg & ~31); size_t stride = 32; while (p < end) {       asm volatile("fdc (%0)\n\t"                     "sync\n\t"                     "fic,m %1(%%sr4, %0)\n\t"                     "sync" : "+r"(p) : "r"(stride) : "memory"); } }

13. Smuggle in the extra functionIndex and vtableOffset values into cpp_vtable_call. Taking the hppa example the abi docs say that register r21 and r22 aren't used for passing arguments and the caller needs to save them, so we can use those probably, i.e..

void cpp_vtable_call( void ) {   register unsigned long r21 asm("r21"); register unsigned long r22 asm("r22"); unsigned long functionIndex = r21; unsigned long vtableOffset = r22; fprintf(stderr, "got to cpp_vtable_call with %x %x\n", functionIndex, vtableOffset); }

and munge the extra values we want into these registers

const int codeSnippetSize = 44; unsigned char *codeSnippet(unsigned char* code, sal_Int32 functionIndex,     sal_Int32 vtableOffset, bool bHasHiddenParam) {    if (bHasHiddenParam) functionIndex |= 0x80000000; unsigned char * p = code; *(unsigned long*)&p[0] = 0xeaa00000; // b,l 0x8,r21 *(unsigned long*)&p[4] = 0xd6a01c1e; // depwi 0,31,2,r21 *(unsigned long*)&p[8] = 0x4aa10040; // ldw 32(r21),r1 *(unsigned long*)&p[12] = 0x22A00000 | L21(functionIndex); // ldil L,r21 *(unsigned long*)&p[16] = 0x36B50000 | R11(functionIndex); // ldo R,r21 *(unsigned long*)&p[20] = 0x22C00000 | L21(vtableOffset); // ldil L,r22 *(unsigned long*)&p[24] = 0x36D60000 | R11(vtableOffset); // ldo R,r22 *(unsigned long*)&p[28] = 0x0c201094; // ldw 0(r1),r20 *(unsigned long*)&p[32] = 0xea80c000; // bv r0(r20) *(unsigned long*)&p[36] = 0x0c281093; // ldw 4(r1),r19 *(unsigned long*)&p[40] = ((unsigned long)(cpp_vtable_call) & ~2); return code + codeSnippetSize; }
 * 1) define unldil(v) (((v & 0x7c) << 14) | ((v & 0x180) << 7) | ((v & 0x3) << 12) | ((v & 0xffe00) >> 8) | ((v & 0x100000) >> 20))
 * 2) define L21(v) unldil(((unsigned long)(v) >> 11) & 0x1fffff) //Left 21 bits
 * 3) define R11(v) (((unsigned long)(v) & 0x7FF) << 1) //Right 11 bits

A build in testtools should show we're getting reasonable values for functionIndex and vtableOffset, before it falls over and dies. So now to gather the normal arguments, for hppa the abi docs say that there are 4 general purpose registers for passing arguments, so ignoring floating points for now we can do...

void cpp_vtable_call( sal_uInt32 in0, sal_uInt32 in1, sal_uInt32 in2, sal_uInt32 in3, sal_uInt32 firstonstack ) {    register sal_Int32 r21 asm("r21"); register sal_Int32 r22 asm("r22"); sal_Int32 functionIndex = r21; sal_Int32 vtableOffset = r22; long sp = (long)&firstonstack; sal_uInt32 gpreg[hppa::MAX_GPR_REGS]; gpreg[0] = in0; gpreg[1] = in1; gpreg[2] = in2; gpreg[3] = in3; double fpreg[hppa::MAX_SSE_REGS]; //todo fprintf(stderr, "got to cpp_vtable_call with %x %x\n", functionIndex, vtableOffset); for (int i = 0; i < hppa::MAX_GPR_REGS; ++i) fprintf(stderr, "reg %d is %d\n", i, gpreg[i]); }
 * 1) ifdef CMC_DEBUG
 * 1) endif

i.e. stuff the values of the 4 registers into gpreg, and get the stack pointer by taking the address of the first argument which we know should be on the stack. We dump the contents of the registers, back in testtools, the first call from c++ to uno is at xLBT->setValues in bridgetest.cxx, so if we dump the first few args there and compare to the above, then we should see the same values appearing (modulo the "this" argument which will be passed in as the first arg before the rest)

14. We just want enough code in cppcall to get this to compile and to get the pAdjustedThisPtr through to callVirtualMethod, so fill enough code in to make things compile and focus on getting through to uno2cpp.cxx's callVirtualMethod, i.e. make sure the pThis argument is derived correctly for your arch and basically forget about anything else.

void callVirtualMethod(void * pThis, sal_uInt32 nVtableIndex,   void * pRegisterReturn, typelib_TypeClass eReturnType,    sal_uInt32 *pStack, sal_uInt32 nStack) {   sal_uInt32 pMethod = *((sal_uInt32*)pThis); pMethod += 4 * nVtableIndex; pMethod = *((sal_uInt32 *)pMethod); typedef void (* FunctionCall )( sal_uInt32, sal_uInt32, sal_uInt32, sal_uInt32 ); FunctionCall pFunc = (FunctionCall)pMethod; (*pFunc)(pStack[0], 1, 2, 3); }

In this case we're assuming we've based this on the m68k one where everything is passed on the stack, so the "this" argument (which is the only one correct at the moment) is the first on our passed in stack, this should get Test_impl::setValues called with 1 2 3 as the first three arguments, before it falls over and dies.

15. Assuming that works, then we're basically done, except for house keeping. We can go back and using the ABI docs for the platform now correctly organize the rest of the arguments into their appropriate registers and stack positions, return conventions for various sized structs etc. Leaving floating points as the last thing to be done.

e.g. for large/complex return types hppa passes the address to place the return value into as r28, which is similar to ia64 or m68k as opposed to other platforms which push a prepended argument into registers/stack before the this pointer.

Liens et Informations complémentaires

 * Exemple (en anglais): Port sur Linux Alpha (ainsi que le patch très complet qui est attaché à cette issue)
 * Attention : [FIXME:partie à revérifier lors de l'implémentation du bridge]les réels (en virgule flottante) simple et double précision sont typiquement retournés dans des registres en virgule flottante plutôt que dans les registres classiques. Il n'y a typiquement pas d'autre utilisation de réels simple/double précision dans un pont UNO, et donc il se peut que le contenu des registres en virgule flottante ne soient pas modifiés entre le "return" de la method d'appel callVirtualMethod et le "return" de la méthode source cpp_vtable_call, c'est à dire que le contenu traverse le pont sans modification et que le résultat correct en sorte. C'est vraiment un peu douteux et risqué de faire confiance à cette possibilité, car quelques ponts peuvent avoir des problèmes (utilisant ces registres pour autre chose, entre temps), et il vaut mieux, bien entendu sauvegarder la valeur correcte du réel en double précision/virgule flottante dans le stockage naturel fourni, et fourni dans la callVirtualMethod, et l'écrire ensuite de façon explicite dans cpp_vtable_call
 * Avec UNO, alors que des types et des structures arbitraires peuvent être retournés, le compilateur idl génère toujours du code dans lequel les structures sont passées par référence, et donc vous n'avez pas à vous soucier des règles concernant ces structures, sachant seulement qu'une structure va retourner une structure.


 * Il y a une implémentation par défaut dans sal/osl/unx/interlck.c, relative à l'utilisation de pthreads pour les verrous croisés (interlocks). Très schématiquement, les ports majeurs ont une implémentation bas niveau (assembleur), et les autres une implémentation logicielle. Il peut être très avantageux pour un port "vivant" d'avoir une implémentation bas niveau de cette partie du code, plutôt que d'utiliser une solution logicielle (beaucoup plus lente).