Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[Assembleur] cmpxchgq ?

20 réponses
Avatar
JKB
Bonjour à tous,

Pardonnez-moi cette question à la limite de la charte, mais j'ai un
petit problème avec un bout d'assembleur amd64 trivial (en notation
AT&T) :

.text
.global try_lock_amd64
.type try_lock_amd64, @function
.align 16

try_lock_amd64:
// Setup the local variables
pushq %rbp
movq %rsp, %rbp
movq 16(%rbp), %rcx /* %rcx = &m->holder */
movq 24(%rbp), %rdx /* %rdx = me */

// Test the lock :
// if (%rcx) == $0, write %rdx into (%rcx)
// if not, write (%rcx) into %rax.
movq $0, %rax
lock
cmpxchgq %rdx, (%rcx)
jz t1

// What is the result
movq 16(%rbp), %rax /* %rax = &m->holder */
movq (%rax), %rax /* %rax = m->holder */
cmpq 24(%rbp), %rax /* m->holder == me */
sete %al
andq $255, %rax

popq %rbp
ret

t1: pushq %rdx
movq (%rcx), %rdx
pushq %rdx
jmp _Z13dbg$backtracev

Cette fonction fait partie d'un bout d'OS que je passe en 64 bits.
En 32 bits, ça fonctionne parfaitement. Ce bout de code est appelé
depuis une fonction en C (équivalente à mutex_trylock() pour des
mutexes récursifs).

Mon problème est simple. Le 'cmpxchgq' ne semble pas faire ce qu'il
devrait (ou alors, je n'ai rien compris).

Je viens de lire et de relire la doc (origine AMD) et si j'ai bien
tout compris, CMPXCHGQ est censé comparer la valeur de %RAX à la
valeur (%RCX). Si ces deux valeurs sont égales, il copie la valeur
de %RDX dans %(RCX) et met le drapeau Z à 1.

Dans mon cas, le programme saute à ma routine de débug qui m'affiche
la pile. J'en déduis donc que ce drapeau est à 1 et que CMPXCHGQ
aurait dû copier %RDX dans (%RCX). Je vois bien que %RDX contient la
bonne valeur, mais (%RCX) reste nul comme le prouve la sortie :

SP -> [$0000000001049428]
+056: [$0000000001049430] -> $0000000000000000 <- pourquoi 0 ?!
+064: [$0000000001049438] -> $00000000010498D0 <- valeur correcte de %rcx
+072: [$0000000001049440] -> $0000000001049490
+080: [$0000000001049448] -> $0000000001001FDD
+088: [$0000000001049450] -> $0000003A00000001

Je suppose que j'ai raté quelque chose, mais je ne vois pas quoi.

Merci de toute lumière,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr

10 réponses

1 2
Avatar
JKB
Le Wed, 03 Nov 2010 06:44:00 +0100,
Pierre Maurette écrivait :
JKB, le 11/2/2010 a écrit :

[...]

Il y a un truc bizarre lorsque je remplace 0 par une valeur non
nulle. Je vais laisser tomber pour ce soir...



C'est également ce que j'ai fait hier soir. Mais je suis tombé du lit
ce matin. J'ai fait quelques tests, et cmpxchg fonctionne comme vous et
moi l'attendions:



Je vais m'y remettre. Ça doit être gros comme une maison et ça
risque de me sauter aux yeux...

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Antoine Leca
JKB écrivit :
Le Tue, 2 Nov 2010 15:51:04 +0000 (UTC),
cmpxchgq %rdx, (%rcx)

En 32 bits, ça fonctionne parfaitement.





En 32 bits, tu ne peux pas utiliser cmpxchg16b, donc je suppose que tu
utilises cmpxchg8b à la place (ce ne sont pas les mêmes instructions.)


Mon problème est simple. Le 'cmpxchgq' ne semble pas faire ce qu'il
devrait (ou alors, je n'ai rien compris).





As-tu vérifié préalablement que cette instruction était implémentée dans
le processeur que tu utilises ? (bit CX16 du CPUID, bit 13 de ECX, cf.
http://www.sandpile.org/ia32/cpuid.htm)


Antoine
Avatar
JKB
Le Tue, 2 Nov 2010 13:13:54 -0700 (PDT),
Jean-Christophe écrivait :
On Nov 2, 5:48 pm, JKB

Gnî !

Bon, je me lance :

1. As-tu essayé de changer cmpxchgq par une suite
de codes équivalents pour comparer le résultat ?
Un truc de ce genre ( si j'ai bien tout suivi )
cmp eax,edx
bne skip
mov edx,ecx
skip ...



Objection. Je cherche à écrire un mutex récursif en _64_ bits (en
32, ça fonctionne). Tout le code est _64_ bits. Et le mutex impose
une opération _atomique_.

2. Il me semble que cmpxchgq compare le
RESTE d'un modulo 8, 16 ou 32 selon le cas.
Sachant que ton code tourne en 32 bits mais pas en 64,
y a t'il une chance que ce soit du à ce remainder ?

"CMPXCHG r/m8, r8 Compare AL with r/m8. If equal, ZF is set and r8
is loaded into r/m8. Else, clear ZF and load r/m8 into AL."
"CMPXCHG r/m16, r16 Compare AX with r/m16. If equal, ZF is set and
r16 is loaded into r/m16. Else, clear ZF and load r/m16 into AL"
"CMPXCHG r/m32, r32 Compare EAX with r/m32. If equal, ZF is set and
r32 is loaded into r/m32. Else, clear ZF and load r/m32 into AL"



CMPXCHG fonctionne aussi en 64 bits comme une extension du mode 32
bits.

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
JKB
Le Wed, 03 Nov 2010 09:50:56 +0100,
Antoine Leca écrivait :
JKB écrivit :
Le Tue, 2 Nov 2010 15:51:04 +0000 (UTC),
cmpxchgq %rdx, (%rcx)





En 32 bits, ça fonctionne parfaitement.





En 32 bits, tu ne peux pas utiliser cmpxchg16b, donc je suppose que tu
utilises cmpxchg8b à la place (ce ne sont pas les mêmes instructions.)



J'ai trouvé et j'ai honte. Je ne sais pas pourquoi, mais j'ai
mélangé les deux arguments de ma fonction... En les remettant dans
l'ordre ça fonctionne tout de suite beaucoup mieux. On devrait
toujours laisser reposer ses problèmes une bonne nuit...


Mon problème est simple. Le 'cmpxchgq' ne semble pas faire ce qu'il
devrait (ou alors, je n'ai rien compris).





As-tu vérifié préalablement que cette instruction était implémentée dans
le processeur que tu utilises ? (bit CX16 du CPUID, bit 13 de ECX, cf.
http://www.sandpile.org/ia32/cpuid.htm)



Naturellement.

Pour ceux que ça intéresse, le code est :

#ifdef AMD64
.text
.global try_lock_amd64
.type try_lock_amd64, @function
.align 16

try_lock_amd64:
// Setup the local variables
pushq %rbp
movq %rsp, %rbp
movq 24(%rbp), %rcx /* %rcx = &m->holder */
movq 16(%rbp), %rdx /* %rdx = me */

// Test the lock :
// if (%rcx) == $0, write %rdx into (%rcx)
// if not, write (%rcx) into %rax.
movq $0, %rax
lock
cmpxchgq %rdx, (%rcx)

// What is the result
movq (%rcx), %rcx /* %rcx = m->holder */
cmpq 16(%rbp), %rcx /* m->holder == me */
sete %al
andq $0xff, %rax

popq %rbp
ret
#endif

Cordialement,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Gump
try_lock_amd64:
// Setup the local variables
pushq %rbp
movq %rsp, %rbp
movq 24(%rbp), %rcx /* %rcx =&m->holder */
movq 16(%rbp), %rdx /* %rdx = me */

// Test the lock :
// if (%rcx) == $0, write %rdx into (%rcx)
// if not, write (%rcx) into %rax.
movq $0, %rax
lock
cmpxchgq %rdx, (%rcx)

// What is the result
movq (%rcx), %rcx /* %rcx = m->holder */
cmpq 16(%rbp), %rcx /* m->holder == me */
sete %al
andq $0xff, %rax

popq %rbp
ret
#endif




Bon, c'est pas bientôt fini, vos grossièretés ? allez donc faire ça
ailleurs !
Avatar
Michel__D
JKB a écrit :


Je viens de lire et de relire la doc (origine AMD) et si j'ai bien
tout compris, CMPXCHGQ est censé comparer la valeur de %RAX à la
valeur (%RCX). Si ces deux valeurs sont égales, il copie la valeur
de %RDX dans %(RCX) et met le drapeau Z à 1.






Avec ma doc, CMPXCHG en 32 bit donne ceci :

CMPXCHG Operande1, Operande2

CMPXCHG compare *Operande1* à AL, AX, EAX et
s'il y a égalité *Operande2* est copié dans *Operande1*
sinon *Operande1* est copié dans AL, AX, EAX.
Avatar
JKB
Le Wed, 03 Nov 2010 19:05:42 +0100,
Michel__D écrivait :
JKB a écrit :


Je viens de lire et de relire la doc (origine AMD) et si j'ai bien
tout compris, CMPXCHGQ est censé comparer la valeur de %RAX à la
valeur (%RCX). Si ces deux valeurs sont égales, il copie la valeur
de %RDX dans %(RCX) et met le drapeau Z à 1.






Avec ma doc, CMPXCHG en 32 bit donne ceci :

CMPXCHG Operande1, Operande2

CMPXCHG compare *Operande1* à AL, AX, EAX et
s'il y a égalité *Operande2* est copié dans *Operande1*
sinon *Operande1* est copié dans AL, AX, EAX.



Certes, mais je suis en _64_ bits et en notation AT&T et non Intel.
Mon esprit est formaté AT&T et Motorola et j'ai toujours eu un peu
de mal avec la notation Intel.

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
jj
Le 02/11/2010 21:13, Jean-Christophe a écrit :
On Nov 2, 5:48 pm, JKB

Gnî !

Bon, je me lance :

1. As-tu essayé de changer cmpxchgq par une suite
de codes équivalents pour comparer le résultat ?
Un truc de ce genre ( si j'ai bien tout suivi )
cmp eax,edx
bne skip
mov edx,ecx
skip ...

2. Il me semble que cmpxchgq compare le
RESTE d'un modulo 8, 16 ou 32 selon le cas.
Sachant que ton code tourne en 32 bits mais pas en 64,
y a t'il une chance que ce soit du à ce remainder ?

"CMPXCHG r/m8, r8 Compare AL with r/m8. If equal, ZF is set and r8
is loaded into r/m8. Else, clear ZF and load r/m8 into AL."
"CMPXCHG r/m16, r16 Compare AX with r/m16. If equal, ZF is set and
r16 is loaded into r/m16. Else, clear ZF and load r/m16 into AL"
"CMPXCHG r/m32, r32 Compare EAX with r/m32. If equal, ZF is set and
r32 is loaded into r/m32. Else, clear ZF and load r/m32 into AL"

HTH ...








Le Tue, 2 Nov 2010 15:51:04 +0000 (UTC),
JKB écrivait :

Bonjour à tous,



Attention, Xpost et Fu2. Le Xpost se fait sur des groupes où je suis
totalement hors charte mais où risquent de se trouver des personnes
pouvant m'aider à trouver une solution. Merci de me pardonner cette
pollution.

Quelques précisions :
- j'utilise qemu pour tester (émulation d'un processeur core2 mono
coeur).
- la machine hôte est une machine avec un core2duo et un système
Linux/debian.
- la fonctio en question fait partie de la root task d'un OS
tournant au-dessus d'un micronoyau L4 (64 bits).
- l'assembleur utilisé est GNU as. J'ai naturellement vérifié le
code objet en le désassemblant.

Pardonnez-moi cette question à la limite de la charte, mais j'ai un
petit problème avec un bout d'assembleur amd64 trivial (en notation
AT&T) :



.text
.global try_lock_amd64
.type try_lock_amd64, @function
.align 16



try_lock_amd64:
// Setup the local variables
pushq %rbp
movq %rsp, %rbp
movq 16(%rbp), %rcx /* %rcx =&m->holder */
movq 24(%rbp), %rdx /* %rdx = me */



// Test the lock :
// if (%rcx) == $0, write %rdx into (%rcx)
// if not, write (%rcx) into %rax.
movq $0, %rax
lock
cmpxchgq %rdx, (%rcx)
jz t1



// What is the result
movq 16(%rbp), %rax /* %rax =&m->holder */
movq (%rax), %rax /* %rax = m->holder */
cmpq 24(%rbp), %rax /* m->holder == me */
sete %al
andq $255, %rax



popq %rbp
ret



t1: pushq %rdx
movq (%rcx), %rdx
pushq %rdx
jmp _Z13dbg$backtracev



Cette fonction fait partie d'un bout d'OS que je passe en 64 bits.
En 32 bits, ça fonctionne parfaitement. Ce bout de code est appelé
depuis une fonction en C (équivalente à mutex_trylock() pour des
mutexes récursifs).
Mon problème est simple. Le 'cmpxchgq' ne semble pas faire ce qu'il
devrait (ou alors, je n'ai rien compris).
Je viens de lire et de relire la doc (origine AMD) et si j'ai bien
tout compris, CMPXCHGQ est censé comparer la valeur de %RAX à la
valeur (%RCX). Si ces deux valeurs sont égales, il copie la valeur
de %RDX dans %(RCX) et met le drapeau Z à 1.
Dans mon cas, le programme saute à ma routine de débug qui m'affiche
la pile. J'en déduis donc que ce drapeau est à 1 et que CMPXCHGQ
aurait dû copier %RDX dans (%RCX). Je vois bien que %RDX contient la
bonne valeur, mais (%RCX) reste nul comme le prouve la sortie :



SP -> [$0000000001049428]
+056: [$0000000001049430] -> $0000000000000000<- pourquoi 0 ?!
+064: [$0000000001049438] -> $00000000010498D0<- valeur correcte de %rcx
+072: [$0000000001049440] -> $0000000001049490
+080: [$0000000001049448] -> $0000000001001FDD
+088: [$0000000001049450] -> $0000003A00000001



Je suppose que j'ai raté quelque chose, mais je ne vois pas quoi.
Merci de toute lumière,
JKB



--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=>http://grincheux.de-charybde-en-scylla.fr





Moais, l'emploi d'instruction miraculeuses qui font tout en un opcode
sont souvent très difficiles à manipuler...

On passe des heures à mettre au point parceque l'on ne connait pas bien
tous les détails et finalement on passe 80% de son énergie sur 2 octets !

le RISC est moins risqué !!

JJ
Avatar
JKB
Le Wed, 03 Nov 2010 21:53:45 +0100,
jj écrivait :
Moais, l'emploi d'instruction miraculeuses qui font tout en un opcode
sont souvent très difficiles à manipuler...



Je suis d'accord. Mais explique moi comment tu fais la même chose
(c'est-à-dire une opération _atomique_ pour un mutex) autrement.

On passe des heures à mettre au point parceque l'on ne connait pas bien
tous les détails et finalement on passe 80% de son énergie sur 2 octets !

le RISC est moins risqué !!



Pour ce même problème ? Non. Et personnellement, je cause sparc v8
et v9 dans le texte et le sparc v9 peut être assez rapidement
rigolo.

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Pierre Maurette
(supersedes )

JKB, le 11/2/2010 a écrit :

[...]

Il y a un truc bizarre lorsque je remplace 0 par une valeur non
nulle. Je vais laisser tomber pour ce soir...



C'est également ce que j'ai fait hier soir. Mais je suis tombé du lit
ce matin. J'ai fait quelques tests, et cmpxchg fonctionne comme vous et
moi l'attendions:

Ubuntu Maverick64 VirtualBox, gcc 4.4.5, as 2.20.51
On peut entrer des valeurs > 0xFFFF, mais l'affichage est tronqué.
[supersedes: j'ai relu et mis au propre ce petit bout avant de
l'archiver, j'ai corrigé quelques erreurs et maladresses, même s'il en
reste encore très certainement]
<testcmpxchg.c>
/*
* cmpxchg rdx, (var)
* compare (var) et rax
* si égal (ZF == 1): rdx -> (var)
* sinon (ZF == 0): (var) -> rax
*/


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint64_t test(uint64_t* var, uint64_t rdx, uint64_t rax);

#define WORD(ad, n) (*((uint16_t*)(ad) + (n)))
enum retours {VALASM, ZF, RDX, RAX};

static inline void _doit(uint64_t val, uint64_t rax, uint64_t rdx){
printf("ZF:%d rax:%04X rdx:%04X val:%04Xn"
, val == rax , (uint16_t)rax, (uint16_t)rdx, (uint16_t)val);
uint64_t res = test(&val, rdx, rax);
printf("ZF:%d rax:%04X rdx:%04X val:%04X (val dans asm:%04X)n"
, !!WORD(&res, ZF), WORD(&res, RAX), WORD(&res, RDX)
, (uint16_t)val, WORD(&res, VALASM));
return;
}

int main(void) {
_doit(0x12, 0x0, 0x5678);
_doit(0x1245, 0x1245, 0x5678);
return EXIT_SUCCESS;
}
</testcmpxchg.c>

<testcmpxchg.asm>
// uint64_t test(uint64_t* var, uint64_t rdx, uint64_t rax);
// RAX test( RDI, RSI, RDX);
.globl test
test:
mov %rdx, %rax
mov %rdi, %rcx
mov %rsi, %rdx
mov $-1, %rsi
lock
cmpxchg %rdx, (%rcx)
jz suite
mov $0, %rsi
suite:
shl $0x10, %rax
mov %dx, %ax
shl $0x10, %rax
mov %si, %ax
shl $0x10, %rax
mov (%rcx), %rdx
mov %dx, %ax
//rax contient [ax][dx][zflag][var]
fin:
ret
</testcmpxchg.asm>

Bon courage, bonne journée.

--
Pierre Maurette
1 2