- "asm" ve register belirteci "r"
İlk önce 'r' belirtecinin "asm" de kullanılışına bir bakalım.
Örneğimiz GCC'nin nasıl yazmaç ayırdığını ve değerleri nasıl
güncelleştirdiğini göstermektedir.
int main(void)
{
int x = 10, y;
asm ("movl %1, %%eax;
"movl %%eax, %0;"
:"=r"(y) /* y çıktı terimidir */
:"r"(x) /* x girdi terimidir */
:"%eax"); /* %eax geri dönen yazmaç */
}
Bu örnekte x'in değeri "asm" içinde y'ye kopyalandı. x ve y "asm"den
yazmaçların içinde geçtiler. Bunun için üretilecek Sembolik Makina Dili
kodu şuna benzer:
main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
movl $10,-4(%ebp)
movl -4(%ebp),%edx /* x=10 %edx te tutulur */
#APP /* asm burada başlıyor */
movl %edx, %eax /* x %eax e taşınır*/
movl %eax, %edx /* y edx e taşınır ve güncelleştirilir */
#NO_APP /* asm burada bitiyor */
movl %edx,-8(%ebp) /* y nin yığıttaki değeri %edx in değeri ile
güncelleştirilir.*/
'r' belirteci kullanıldığında GCC herhangi bir yazmacı ayırmakta serbesttir.
Örneğimizde x'i tutmak için %edx'i
seçmiştir. x'in değerini %edx'ten
okuduktan sonra y için yine %edx'i
seçmiştir.
y'nin değeri çıktı terimi bölümünde oldukça
%edx'in güncelleştirilen değeri
;y'nin yığıttaki yeri, -8(%ebp)'de
belirtilir. Eğer y girdi bölümünde tanımlanmış olsa
idi, geçici y yazmacı (%edx) güncelleştirilmesine
rağmen y'nin yığıttaki yeri güncelleştirilmezdi.
%eax geri dönen yazmaçlar listesinde oldukça GCC onu
bilgi tutmak dışında kullanmayacaktır.
Çıktılar oluşmadan önce girdilerin yok olduğu farz edilerek, hem
x girdisi hem y çıktısı aynı
%edx yazmacında tutuldular. Ama şunu unutmayın eğer
bir kaç komut işletecekseniz bunu yapamazsınız. Girdi ve çıktıların
farklı yazmaçlarda tutulduğundan emin olmak için, & belirteç
değiştiricisini kullanabiliriz.
Bununla ilgili bir örnek :
int main(void)
{
int x = 10, y;
asm ("movl %1, %%eax;
"movl %%eax, %0;"
:"=&r"(y) /* y çıktı terimidir ve
& belirteç değiştiricisidir. */
:"r"(x) /* x girdi terimidir */
:"%eax"); /* %eax geri dönen yazmaçtır*/
}
Ve burada bu örnek için üretilmiş Sembolik Makina Dili kodunu
bulabilirsiniz, x ve y'nin
"asm" de farklı yazmaçta tutulduğu görülmektedir.
main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
movl $10,-4(%ebp)
movl -4(%ebp),%ecx /* x, girdi %ecx tedir */
#APP
movl %ecx, %eax
movl %eax, %edx /* y, çıktı %edx tedir */
#NO_APP
movl %edx,-8(%ebp)
- Özel yazmaç belirteçlerinin kullanımı
Şimdi kişisel yazmaçları terimler için belirteç olarak kullanmaya bir
bakalım. Aşağıdaki örneğimizde cpuid komutu girdiyi
%eax yazmacından alıyor ve çıktıyı dört yazmaca
veriyor: %eax, %ebx,
%ecx, %edx. cpuid
girdisi, op değişkeni, "asm"ye
%eax cpuid'nin de beklediği gibi yazmacından giriyor.
a, b, c ve
d belirteçleri çıktıda dört yazmaçtaki değerleri
kendilerinde toplamak için kullanılmıştır.
asm ("cpuid"
: "=a" (_eax),
"=b" (_ebx),
"=c" (_ecx),
"=d" (_edx)
: "a" (op));
Aşağıda bunun için üretilmiş Sembolik Makina Dili kodunu görebilirsiniz
(_eax ,_ebx vb ... değişkenlerin yığıtta bulunduğu varsayılmıştır):
movl -20(%ebp),%eax /* 'op' yi %eax te tut -- girdi */
#APP
cpuid
#NO_APP
movl %eax,-4(%ebp) /* %eax i _eax te tutar -- çıktı */
movl %ebx,-8(%ebp) /* diğer yazmaçları kendi çıktı
movl %ecx,-12(%ebp) değişkenlerinde tutar */
movl %edx,-16(%ebp)
Aşağıdaki yolda strcpy fonksiyonu "S"
ve "D" belirteçleri kullanılarak uygulanabilir:
asm ("cld\n
rep\n
movsb"
: /* girdi yok */
:"S"(src), "D"(dst), "c"(count));
Kaynak gösterge src %esi'ye
"S" belirteci ve hedef gösterge
dst'ye "D" belirteci kullanılarak
yerleştirilmiştir. Sayaç değeri rep önekinin
gerektirdiği gibi %ecx'e yerleştirilmiştir.
Ve burada da 32-bit kodları birleştirip 64-bit kod elde etmek için,
%eax ve %edx olmak üzere iki
yazmaç kullanan bir belirteç göreceksiniz:
#define rdtscll(val) \
__asm__ __volatile__ ("rdtsc" : "=A" (val))
Üretilen Sembolik Makina Dili kodu şuna benzer
(val 64 bit bellek alanına sahipse):
#APP
rdtsc
#NO_APP
movl %eax,-8(%ebp) /* A belirtecinin sonucu olarak
movl %edx,-4(%ebp) %eax ve %edx çıktı gibi çalışır */
%edx:%eax'in içindeki değerler 64 bit çıktı gibi çalışır.
- Karşılaştırma belirteçlerinin kullanımı
Burada da dört parametreli sistem çağrıları için kodları göreceğiz:
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
Üstteki örnekte sistem çağrısına dört argüman %ebx, %ecx, %edx
ve %esi'ye b, c, d ve S
belirteçleri kullanılarak bırakılmıştır. "=a" belirteci çıktıda
kullanılmıştır. Bundan dolayı sistem çağrısının dönen değeri %eax,
__res değişkenine aktarılır. Karşılaştırma belirteçlerinden
"0"'ı girdi bölümünün ilk teriminin belirteci olarak kullanarak,
sistem çağrısı numarası __NR_##name %eax'e atanır ve sistem
çağrısın girdisi olarak işlem görür. Böylece %eax burada hem
girdi hem de çıktı yazmacı olarak çalışır. Bunun için iki farklı yazmaç kullanılmadı.
Ama şu unutulmamalıdır ki girdi (sistem çağrı numarası) çıkış üretilmeden önce
kullanılmalıdır.
- Bellek terimi belirtecinin kullanımı
Aşağıdaki küçük azaltma işlemini inceleyin:
__asm__ __volatile__(
"lock; decl %0"
:"=m" (counter)
:"m" (counter));
Bunun için üretilen Sembolik Makina Dili kodu şöyle olur:
#APP
lock
decl -24(%ebp) /* sayaç bu bellek bölümünde değiştirilir. */
#NO_APP.
Burada sayaç için yazmaç belirteci kullanmadan önce bir düşünmelisiniz.
Eğer kullanırsanız, önce sayacın içeriğini bir yazmaca atamalısınız.
Azaltma işlemini yaptıktan sonra sonucu belleğine atamalısınız. Ama o
zaman da lock kullanma ve kodu küçük tutma
çabamızı boşa çıkarmış oluruz. Bu bellek belirtecinin gereğini kesin
olarak göstermektedir.
- Geri Dönen yazmaçların Kullanımı
Temel Bellek Kopyasının işlemesini inceleyin.
asm ("movl $count, %%ecx;
up: lodsl;
stosl;
loop up;"
: /* çıktı yok */
:"S"(src), "D"(dst) /* girdi */
:"%ecx", "%eax" ); /* geri dönen yazmaçlar */
lodsl %eax'i değiştirirken, lodsl ve
stosl komutları onu dolaylı olarak kullanır.
%ecx yazmacı doğrudan count'u çağırır.
Biz %eax ve %ecx'i geri dönen
yazmaçlar listesinde belirtmediğimiz sürece GCC bunun farkında olmayacaktır.
Bunu yapmadığımız sürecce GCC %eax ve %ecx'in
serbest olduğunu varsayar ve onları başka bilgiler tutmak için kullanabilir.
Şunu bilmelisiniz ki %esi ve %edi "asm"
tarafından kullanılıyor ve geri dönen yazmaçlar arasında belirtilmemişlerdir.
Çünkü onlar zaten girdi terimlerinin listesinde bulunmaktadırlar. Ve son olarak,
eğer dolaylı veya doğrudan kullanılmış bir yazmaç girdi veya çıktı listesinde
bulunmuyorsa, onu geri dönen yazmaçlar listesine yazmalısınız.