x86_64 makine kodu, 4 bayt
BSF (ileri taramayı ileri tara) komutu tam olarak bunu yapar !
0x0f 0xbc 0xc7 0xc3
Gcc tarzı derlemede, bu:
.globl f
f:
bsfl %edi, %eax
ret
Giriş, EDI kaydında verilir ve EAX kaydında standart 64 bit c çağrı kurallarına göre geri gönderilir.
İkisinin tamamlayıcı ikili kodlaması nedeniyle, + ve sayıları olduğu gibi -ve için de çalışıyor.
Ayrıca, "Kaynak işlenenin içeriği 0 ise, hedef işlenenin içeriği tanımsızdır" ifadesine rağmen. , Ubuntu VM'mde çıktısının f(0)
0 olduğunu görüyorum .
Talimatlar:
- Yukarıdakileri kaydedin
evenness.s
ve ile birleştiringcc -c evenness.s -o evenness.o
- Aşağıdaki test sürücüsünü aşağıdaki gibi kaydedin
evenness-main.c
ve derleyin gcc -c evenness-main.c -o evenness-main.o
:
#include <stdio.h>
extern int f(int n);
int main (int argc, char **argv) {
int i;
int testcases[] = { 14, 20, 94208, 7, 0, -4 };
for (i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
printf("%d, %d\n", testcases[i], f(testcases[i]));
}
return 0;
}
Sonra:
- Bağlantı:
gcc evenness-main.o evenness.o -o evenness
- Çalıştırmak:
./evenness
@FarazMasroor, bu cevabın nasıl türetildiği hakkında daha fazla bilgi istedi.
C ile x86 derlemesinin karmaşıklıklarından daha aşinayım , bu yüzden tipik olarak benim için derleme kodu üretmek için bir derleyici kullanıyorum. Ben deneyimlerinden biliyorum gcc gibi uzantıları __builtin_ffs()
, __builtin_ctz()
ve__builtin_popcount()
tipik derlemek ve x86 üzerinde 1 veya 2 talimatlara birleştirin. Böylece şöyle bir c işleviyle başladım :
int f(int n) {
return __builtin_ctz(n);
}
Normal gcc derlemesini nesne koduna tamamen kullanmak yerine, derleme - -S
seçeneğini sadece derleme seçeneğini kullanabilirsiniz gcc -S -c evenness.c
. Bu, böyle bir derleme dosyası verir evenness.s
:
.file "evenness.c"
.text
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
rep bsfl %eax, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
Bunun bir çoğu dışarı atılabilir. Özellikle , imzalı işlevler için c çağrı kuralınınint f(int n);
hoş ve basit olduğunu biliyoruz - giriş paramı EDI
kayıt defterine geçirilir ve dönüş değeri EAX
kayıt defterine döndürülür . Bu yüzden talimatların çoğunu çıkarabiliriz - birçoğu kayıtların kaydedilmesi ve yeni bir yığın çerçeve oluşturulması ile ilgilidir. Yığını burada kullanmıyoruz ve sadece EAX
kayıt defterini kullanıyoruz, bu nedenle diğer kayıtlar için endişelenmenize gerek yok. Bu "golf" derleme kodunu bırakır:
.globl f
f:
bsfl %edi, %eax
ret
@Zwol'un işaret ettiği gibi, benzer bir sonuç elde etmek için optimize edilmiş derlemeyi de kullanabilirsiniz. Özellikle -Os
, yukarıdaki talimatları tam olarak üretir (fazladan nesne kodu üretmeyen birkaç ek montajcı direktifiyle).
Bu, şimdi gcc -c evenness.s -o evenness.o
yukarıda açıklandığı gibi bir test sürüşü programına bağlanan ile birleştirilir.
Bu düzene karşılık gelen makine kodunu belirlemenin birkaç yolu vardır. En sevdiğim şey gdb disass
disassembly komutunu kullanmak :
$ gdb ./evenness
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ./evenness...(no debugging symbols found)...done.
(gdb) disass /r f
Dump of assembler code for function f:
0x00000000004005ae <+0>: 0f bc c7 bsf %edi,%eax
0x00000000004005b1 <+3>: c3 retq
0x00000000004005b2 <+4>: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1)
0x00000000004005bc <+14>: 0f 1f 40 00 nopl 0x0(%rax)
End of assembler dump.
(gdb)
Böylece bsf
komutun makine kodunun 0f bc c7
ve için ret
olduğunu görebiliriz c3
.