Ég ákvað að henda hérna inn smá kynningu (reyndar soldið löng grein!) á uppáhalds forritunarmálinu mínu, en það er Assembly.
Varúð! Það er nokkuð af enskuslettum þegar ég kann ekki íslensku heitin, eða þegar enskan á betur við.

Þið gætuð spurt: Hvað er eiginlega Assembly ?

Assembly er mjög low-level forritunarmál (vs C/C++ sem eru high-level), þar sem maður er að forrita örgjörvan sjálfan, og býður þess málið þessvegna upp á mesta hraða sem hægt er að ná fram. Þess vegna hentar Assmebly mjög vel í algorithmum sem krefjast mjög margra útreikninga á sekúndu. Sem dæmi má nefna, realtime-raytracers ,3d kernela t.d. opengl og dulkóðun. Assembly er einnig mikið notað í svokölluðum Embedded systems, t.d. PIC örgjörvar, þar sem stærð skiptir öllu máli.

Margir gera sér ekki grein fyrir því að það er einnig hægt að gera Windows, Unix og Mac forrit í hreinu Assembly. Ég ætla þó að fjalla að mestu um Assembly í Windows.

Einsog fyrr segir er Assembly hratt. Og vegna þess að það er low-level, þá gefur það mikið betri stjórn og sveigjanleika yfir tölvunni. Assembly er einnig eina aðferðin til að nýta nýustu tækni sem örgjörvar hafa uppá að bjóða. Þá er ég að tala um SIMD tæknina, SSE, SSE2, SSE3 og SSE4 (sem er enn ekki komin út) og x86-64 (64bit-a intel örrarnir).

Annar kostur er að maður getur sýnt öllum hversu Uber 1337 tölvu-njörður maður er.



Áður en við byrjum þá þarf aðeins að kynna örgjörvan. Ég er einungis að fjalla um x86 örgjörvana en það eru örgjörvarnir sem eru notaðir í PC tölvum en það eru örgjörvar frá Intel, AMD og Cyrix (þeir eru farnir á hausinn, en þó eru en til nokkrir). Þetta á því ekki við Mac og PowerPC örgjörvana.

x86 örgörvarnir innihalda 8 “general purpose registers” þ.e. 8 hólf sem við getum geymt hvað sem er í. En þeir eru samansettir svona:

|         EAX           | |            EBX           |   32-bit registerar
------------------------------------------------------
           |     ax     |                 |    bx     |   16-bit         
------------------------------------------------------
           | ah  |  al  |              |  bh |  bl |   8-bit   
|           ECX         | |            EDX           |    32-bit
------------------------------------------------------
            |     cx     |               |     dx     |    16-bit
------------------------------------------------------     
            |  ch |  cl |               |  dh  |  dl |    8-bit
 
|           EDI         |  |            ESI           |    32-bit
-------------------------------------------------------
           |     di     |                  |    si    |      
|           EBP         |  |            ESP           |     32-bit
-------------------------------------------------------
             |    bp    |                |     sp     |     16-bit
             

Það sem þessi mynd á að sýna er ax er beintengdur við neðstu 16 bit eax, og er saman settur úr tveimur 8-bita registerum, ah (a-high) og al (a-low). En þetta er afleiðing af því hvernig örgjörvarnir þróuðust (intel bætti alltaf nýjungum ofaná það gamla). Ax stendur tildæmis fyrir a-extended, og eax fyrir extended-ax.

Samsetning registerana olli mér þó nokkrum ruglingi til að byrja með, en ég ætla að reyna að útskýra þetta hérna. Auðveldast er að sýna þetta í hex. Ef að maður setur FFh (11111111 á binary eða 255 í venjulegum tölum) í AH og EEh í AL, þá inniheldur AX FFEEh. Og EAX inniheldur þá aftur á móti 0000FFEEh. Þannig að ef að við setjum 12345678h í eax, hvað innihalda þá ah og al ? nú það er ah = 56h og al = 78þ Þar af leiðandi er ax = 5678h. Það er mikilvægt að átta sig á því hvernig þetta virkar.

Alla þessa registera er hægt að nota í 32-bita umhverfi, en þó er best að notast við 32-bita registerana: eax, ebx, ecx osfrv.

Þótt að þeir heiti “general purpose” þá hafa sumir þeirra sérstök hlutverk, eax er látin geyma gildi sem föll skila, “return value”.
Dæmi:
  í c/c++               
int myfunc(){                                  
     return 34;				   
}							         
  
í assembly
myfunc:
      mov eax, 34
      ret
Esp er líka látin benda á stackin, ebp er látin benda á svo kallaðar “local variables” og svo er ecx notaður sem teljari, í lykkjum. Dæmi er t.d. for(int i =0; i == somevalue; i++), í assembly myndi ecx taka við hlutverki i.

Það eru nokkrir registerar í viðbót, svokallaðir “segment registers” en þá þarf ekki að nota vegna þess að Windows (og linux einnig) meðhöndlar þá fyrir okkur. Þeir eru ein af ástæðum þess að vont orð fer af Assembly, en það er algjör martröð að eiga við þá í DOS. Vegna þess að Windows sér um þá fyrir okkur þá látum við einsog þeir séu ekki til (en þeir eru þarna!). Ef okkur virkilega langar til, þá er hægt að eiga við “segment registerana” en þá er mjög líklegt að það næsta sem við sjáum sé “blue screen of death”. :D



Það er allt saman gott og blessað en þetta gerir ekkert gagn nema við lærum nokkar assembly skipanir.

Hérna eru nokkrar algengustu:

mov (dest), (value)
Move. Þessi skipun afritar (value) og setur í (dest). Hún leyfir að flytja frá registerum í minnið, úr minninu í registera en ekki úr minni í minni. Plássin þurfa einnig að vera jafn stór.

Dæmi:
               mov eax, 12h        Þetta setur gildið 12h í eax
				           
									    
	         mov ebx, eax        Þetta afritar gildið í eax, og skrifar í ebx
		   mov ecx, myint      Afritar gildið í myint breytunni og setur í ecx
	         mov myint, ecx      Afritar gildið í ecx og setur í myint
		   mov myint, myint2   Ólögleg skipun, minni í minni
	         mov cl, edi         Ólögleg skipun, 32-bit í 8-bit
				           			
			


add (dest), (value)
Add, samlagning. Þessi skipun afritar gildið í (value) leggur við (dest) og skilar útkomunni í (dest). Allar skipanir hafa sömu skilyrði og mov, nema að annað sé tekið fram
Dæmi:
		   mov eax, 0         ; eax inniheldur núna 0
	         add eax, 3         ; núna inniheldur eax 3
		   mov ebx, 5  
		   add eax, ebx       ; núna inniheldur eax 8 og ebx innihedur 5



sub (dest), (value)
Subtract, frádráttur. Þessi skipun er allveg einsog add, nema dregur gildið í value frá gildinu í (dest).

Dæmi:	
	  
        	   mov eax, 10
        	   sub eax, 8         ; núna inniheldur eax 2
        	   sub eax, 2         ; núna inniheldur eax 0
        	   sub eax, 1         ; núna inniheldur eax FFFFFFFFh, eða -1


jmp (location)
Jump. Þessi skipun lætur okkur hoppa á tiltekin stað í kóðanum. Þetta er það sama og goto í c/c++ og öðrum forritunarmálum.

Dæmi:
				   	   
               mov eax, 2
  		   mov ebx, 0
		   jmp mylabel        ; hérna hoppum við áfram
		   add eax, 5         ; þessu er sleppt
		   mov ebx 3          ; líka þessu
	mylabel:                    ; og lendum hér  
		   add eax, 2         ; eax er núna 2+ 2 = 4
		   add ebx, 1         ; ebx = 0 + 1 = 1
			


call (function)
Call. Kallar á fall. Setur einnig núverandi staðsetningu efst á stackin, til þess að hægt sé að koma til baka.

Dæmi:
        			   	
        	   myfunc proc    
        		mov eax, 0
        	      ret                ; ret er það sama og pop ebx
        			             ; 		          jmp ebx
        	   myfunc endp
        			      
        			      
        	   main
        	      call myfunc        ; hvað ætli þetta geri ?

Þetta er það sem gerist þegar við köllum á föll í c/c++. T.d. myfunc(); er í raun call myfunc.



push (value)
Push. Afritar gildið í (value) og setur það efst á stackin. Engar áhyggjur stackurinn verður útskýrður eftir smá.

Dæmi:
      	      push eax          ; setur gildið í eax efst á stackin
      	      push 12	      ; setur 12 efst. eax gildið fyrir neðan    			
                  push myint        ; setur gildi myint efst, 12 neðan, eax neðst

pop (dest)
Pop. Fjarlægir efsta gildi stacksins og setur í (dest). (dest) má vera register eða pláss í minni.

Dæmi:
     			push eax          ; ath. hefur engin áhrif á það sem er í eax.
     			pop  ebx          ; ebx inniheldur núna sama gildi og er í eax
     					    
     			push myint
     			pop myint2        ; gerir það sama og myint2 = myint; í C.
     			



Þá er komið að því að finna út hvað stackurinn er. Minnið er einsog pósthólf, hvert hólf geymir 1 byte og þau eru númeruð frá 0 til FFFFFFFFh, eða 4.294.967.295, allveg sama hve mikið minni raunverulega er í tölvunni. Það er sérstakur staður í minninu sem að kallast stack. Ólíkt venjulegu minni, þá vex stackinn niður á við í minninu. Esp registerinn bendir alltaf á “efsta gildið”, en það
er neðst í minninu. Stackin lýtur í raun svona út:
	stackurinn
     ------------
     |                    |   <-- esp bendir hingað, og vex svo niður við hvert push
     |----------|          
     |                    |    <-- bendir hingað eftir eitt push
     |----------|	
     |                    |    <-- bendir hingað eftir annað push, og aftur hér beint 
     |----------|		 fyrir ofan eftir eitt pop.
     |                    |
     ------------
     
     
	Dæmi:
                    mov esp, 40             ; lætur esp benda á byte númer 40 í minninu.
                    mov eax, 24
                    push eax              ; eax er 4 byte, esp minnkar um 4
                    push 12h              ; pushast venjulega 32-bit, esp = 32
		
                pop ebx               ; ebx er núna = 12h , og esp hækkar um 4.
                pop edx               ; edx = eax og esp = 40

Maður verður að fara varlega þegar maður er að vinna með stackin, ef að við hefðum breytt esp eitthvað á milli push og pop. Þá fáum við ekki það sem við viljum út og mjög góðar líkur er á að allt crashi. Einnig verður að passa að á móti hverju pushi kemur pop. Annars stækkar stackinn bara og endar með að skrifa yfir eitthvað mikilvægt í minninu.

Þá erum við tilbúin að gera okkar fyrsta assembly forrit, en fyrst skuluð þið fara á http://website.assemblercode.com/masm32/m32v10r.zip . Unzipið og installið á c: drivið, þetta er um 3,7 mb download en tekur um 40 mb eftir install. Þetta er masm32 pakkin sem ástrali að nafni Hutch viðheldur. Hann inniheldur Microsoft MASM , öll helstu libraryin og fullt af tækjum og hjálpargögnum sem hægt er að nota við að gera windows assembly forrit.

Pakkin er allveg ókeipis og einu skilyrðin eru að það er bannað að selja hann. Microsoft gefur MASM, en hann heitir núna ml.exe, fyrir svokallað “educational usage”, og má þá ekki búa til forrit sem ætlað er að selja, eða fyrir annað stýrikerfi en windows. S.s. bannað er að nota ml.exe til að gera linux forrit samkvæmt EULA-inu. Ég ætla samt að leyfa mér að efast um að Microsoft komi og sparki niður hurðinni hjá þér, ef að þú gerir “hello world!” forrit í ml.exe fyrir linux. En þið hafið verið aðvöruð.

Allt annað í þessum pakka nema ml.exe, þ.e. libraryin, .inc fælarnir osfrv. er gert af random fólki sem hefur gefið vinnuna sína, ekki undir gpl-leyfinu, heldur algerlega frítt, og má því gera hvað sem er við það.




Assembly forrit eru sett upp á eftir farandi hátt:
	.386
	.model flat, stdcall
	option casemap:none
	
	include \masm32\include\eitthvað-random-inc-file.inc
      include \masm32\lib\samsvarandi-lib-file.lib
        
.data
	fullt af breytum sem eru gefin upphafsgildi
.data? 
	fullt af breytum sem fá ekki upphafsgildi, t.d. bufferar
.const
	constantarnir okkar
.code
	kóðinn okkra
	


.386
Þetta er skipun sem segir assemblernum hvernig örgjörva hann á að gera skipanir fyrir, einnig hægt að gera .486, .586 (pentium 1!) og uppí .686, en öruggast er að halda sig bara við .386 eða .486.

.model flat, stdcall
Þetta er önnur skipun fyrir assemblerinn sem segir okkur hvernig minnið er byggt upp. Á windows og linux er bara einn möguleiki en það er flat. En það merkir að við þurfum ekki að hugsa um segment registerana. Stdcall, segir MASM hvernig við gefum föllum breytur. Aðrir möguleikar eru C og pascal. C virkar þannig að breyturnar eru settar á stackin í röð frá hægri til vinstri. Kallandi sér einnig um að hreinsa til á stacknum.

foo(int firsti, int annar, int þriðji, int fjórði) myndi þá vera svona:

	push fjórði            ; fyrst seinasti
	push þriðji            ; svo næsti
	push annar             ; osfrv.
	push firsti
	call foo
	add esp, 16            ; kallandi lagar til á stacknum, 4 * int = 16 byte.

Pascal aðferðin setur breyturnar á stackin í öfugri röð miðað við C, þ.e frá vinstri til hægri og fallið sér sjálft um að laga stackin til eftir sig. Stdcall er hybrid á milli C og pascal og virkar þannig að breytunum er raðað frá hægri til vinstri og fallið sér um að laga stackin. Windows notast einungis við stdcall nema í einu falli, en það er wsprintf(), þar er notuð C aðferðin.

option casemap:none
Þetta segir MASM að gera greinarmun á há og lágstöfum. Windows krefst þess að þetta sé gert.

.data
.data?
.const
.code

Þetta eru skipanir sem skipt forritinu upp í hluta. Hægt er að gera hvern hluta mörgum sinnum í kóðanum, þar sem að linkerinn sér um að setja alla .data hlutana saman, alla .code hlutana saman osfrv.

Munurinn á .data og .data? er sá að .data? breytunum eru ekki gefin byrjunargildi, og þær taka ekki neitt pláss í .exe skjalinu. T.d. ef að við myndum gera 10.000 byte af breytum í .data þá stækkar .exe skjalið um 10.000 byte, en ekki ef við setjum þær í .data? hlutann.

.code er svo eini hlutinn þar sem assembly skipanirnar koma svo fyrir.


——————————————————————-


Allavegana, kveikið upp qeditor.exe, eða smellið á MASM32 editor shortcutið á skjáborðinu. Þar opnast upp kynning sem gæti verið þess virði að skoða. Við hunsum það og förum í file -> new. Stimplið svo inn:
.386
.model flat, stdcall
option casemap:none
.data
	mystring  db  "hallo heimur!",0      ;zero-terminated string
.code
start:
	mov ebx, 2 
	push ebx
	mov eax, 0
	pop edx
 	jmp mylabel
   	mov eax, 2                           ; ath. að það er hoppað yfir þetta
   	
mylabel:
	sub ebx, ecx    ; ebx = 0
	ret					  ;hér er eax = 0
	
end start
Vistið svo skjalið t.d. tutasm.asm og munið að láta það enda á .asm. Farið svo næst í project –> assemble & link,
svo í project –> run program. Ef að allt er rétt gert, þá gerist ekki neitt og enginn villa.



Málið er að það er heilmikið mál að láta forrit skila einhverju út í windows. T.d. til að gera þetta að venjulegu “halló heimur” forriti þá þurfum við að bæta við heilum helling af libraryum.



; **************************************************************************
;                    Einfallt hallo heimur forrit!
;        Gerið forritið með að velja project - Console assemble & link
; **************************************************************************
	.386
	.model flat, stdcall
      option casemap:none	
	include \masm32\include\windows.inc
	include \masm32\macros\macros.asm
	
 ; -----  prótótýpur fyrir föllin, þessi skjöl eru samskonar og .h skjöl í c/c++
	
        include \masm32\include\masm32.inc
        include \masm32\include\gdi32.inc
        include \masm32\include\user32.inc
        include \masm32\include\kernel32.inc
    	
 ; ------  libraryin sem geyma staðsetningu fallana.
 
        includelib \masm32\lib\masm32.lib
        includelib \masm32\lib\gdi32.lib
        includelib \masm32\lib\user32.lib
        includelib \masm32\lib\kernel32.lib
.data
	minnstrengur  db  "hallo heimur!",0  ; ,13,10 það sama og \n
.code
start:
	print chr$("Haegt er ad segja 'Hallo heimur!' svona ",13,10)
	call main
	mov ebx, input()                         ; svo forritið lokist ekki strax
	ret
main proc
	print chr$("og lika svona: ")
	mov eax, offset minnstrengur             ;eax = &minnstrengur
	print eax                          
	ret
	
main endp
end start



Print er macro sem að tekur inn pointer að zero-terminated streng og skrifar hann í console. Það sem print gerir er að kalla á StdOut(), sem að er skipun sem er geymd í kernel32.dll í c:\windows\system32\ möppunni. Printf() og cout fela þetta fyrir okkur og er hérna verið að líkja eftir þessari hegðun, en það er bara gert með macros.
Input er allveg eins, en það kallar á StdIn() og skilar út pointer á streng sem sleginn var inn.

chr$ er einkar þægilegt macro en það skilgreinir streng í .data hlutanum og skilar út addressuni, sem leyfir okkur þá að skilgreina strengi í .code hlutanum og innan í öðrum macroum.

Ólíkt c og c++ þá er macrovél MASM mjög öflug og hægt er að gera virkilega flókin og öflug macro til þess að aðstoða við forritun. Margir eru á móti macro notknuninni og segja að þetta sé ekki assembly, en macroin eru eina ástæða þess að fólk verði ekki gjörsamlega geðveikt við að höndla stór forrit sem skrifuð eru í assembly. Að nota og gera sín eigin macro er mjög stór hluti af því að læra assembly.




Að lokum er hér eitt forrit sem að breytir streng úr lágstöfum í hástafi, og breytir ekki öðrum stöfum.

; **************************************************************************
;                    Breytir lágstafa streng í hástafi
;        Gerið forritið með að velja project - Console assemble & link         
; **************************************************************************
	.386
	.model flat, stdcall
      option casemap:none	
	include \masm32\include\windows.inc
	include \masm32\macros\macros.asm
	
 ; -----  prótótýpur fyrir föllin, þessi skjöl eru samskonar og .h skjöl í c/c++
	
	include \masm32\include\masm32.inc
	include \masm32\include\gdi32.inc
	include \masm32\include\user32.inc
    	include \masm32\include\kernel32.inc
    	
 ; ------  libraryin sem geyma staðsetningu fallana.
 
  	includelib \masm32\lib\masm32.lib
     	includelib \masm32\lib\gdi32.lib
    	includelib \masm32\lib\user32.lib
     	includelib \masm32\lib\kernel32.lib
.data
	minnstrengur  db  "touppercase  # $ 12345 ",0  
	dummy         db  0                           ;dummy breyta
.code
start:
	print chr$("Nuna breytum vid streng ur lag i hastafi ",13,10)
	push offset minnstrengur
      	push offset dummy - 1
	call toUpper                       
	                                      ; toUpper lagar stackin
      	print offset minnstrengur
	mov ebx, input()                         
	ret                           
toUpper proc 
	      mov edx, [esp + 4]                ; edx = &dummy - 1
      	mov ebx, [esp + 8]                ; ebx = &minnstrengur
      	sub edx, ebx                      ; edx = lengd strengsins
      	mov ecx, 0
      	
      
  toU:
      	cmp ecx, edx                    ; ber saman ecx og edx, komin á endan ?
      	je out1                         ; je merkir jump if equal, hoppeð ef ecx=edx
      
      	mov al, [ebx + ecx]             ;flytja staf í al
      
     	.if al < "a"                     ; ef að al < ascii a þá breytum við ekki
     
           	mov [ebx + ecx], al      ; skila óbreyttu
           	add ecx, 1               ; næsta stak
           	jmp toU                  
           
     	.endif   
  continue:                              ; við erum hér ef að al > ascii a
      	sub eax, 20h                     ; A = a - 20h osrfv.
      	mov [ebx + ecx], al              ; skila nýja gildinu
      	add ecx, 1			 ; næsta stak
      	jmp toU
  out1:                                  ; vegna stdcall, þarf að laga stackin til
      
      	pop ebp                          ; geyma efsta stakið
      	pop ebx                          ; poppa &dummy-1 af
      	pop ebx                          ; poppa &minnstrengur af
      	push ebp                         ; skella ebp til baka á stackin       
	ret                              
	
toUpper endp
end start



Forritið hér fyrir ofan sýnir nokkra nýja hluti, t.d. hvernig maður gerir loopur, notar .if og hvernig maður gefur falli breytur. Þessar þrjár línur :

push offset minnstrengur
push offset dummy - 1
call toUpper

Eru það sama og gera svona í c/c++

toUpper( &(dummy) - 1, &minnstrengur);

Inni í fallinu nálgast svo maður breyturnar af stackinum en það er gert hérna:

mov edx, [esp + 4] ; edx = &dummy - 1
mov ebx, [esp + 8] ; ebx = &minnstrengur

Ef að maður gerir mov edx, [esp] þá merkir það að setja breytuna sem esp bendir á í edx, í stað þess að setja töluna esp inniheldur. Ef að bætum við inn í hornklofan, [esp+4], þá förum við þangað sem esp bendir og 4 bætum ofar, og þar er talan sem or notuð. Þetta heitir offset. Skipunin er þá það sama og edx = *(esp + 4 ). Það er mikilvægt að átta sig á því hvernig þetta virkar. Lesið aftur til um stackin, aðeins ofar, til að fá betri mynd af því hvað er að gerast.

Síðan er ný skipun ‘je’,þetta er svo kallað “conditional jump”, sem merkir það að örgjörvinn skoðar sérstakan flag-register, og ef að hann uppfyllir ákveðin skilyrði, þá er hoppað. Maður notar oft ‘cmp’ , en sú skipun ber saman 2 gildi og setur flag registerinn í samræmi við það hvernig fyrsta gildið er miðað við það seinna. Þá er hægt að fylgja því eftir með einhverri af eftirfarandi, ath sumar gera það sama og eru þarna bara til hægðarauka.
	je              ---        Jump if equal                cmp a, b      a == b
	jne             ---        Jump not equal                             a != b
	ja              ---        Jump above                                 a > b
	jna             ---        Jump not above                             a <= b
	jae             ---        Jump above or equal                        a >= b
	jb              ---        Jump below                                 a < b
	jnb             ---        Jump not below                             a >= b
	jbe             ---        Jump below or equal                        a <= b
	
Margar fleirri eru til en tekur að telja upp hér.

Síðast en ekki síst þá notast forritið við .if, en það virkar einsog í c/c++, og hægt er að nota ==, <=, >= !=, &&, || og einhver fleirri samanburðar tákn. Svona lítur .if klausa út.

.if (skilyrði)
	blabla
.elseif (annað skylrði)
	mmmhmhmhmh
.else 
	bla
.endif
Engin takmörk eru á því hve mörg .elseif maður má nota.


Loopuna sem er í forritinu, ecx er látin vera 0 til að byrja með, síðan er hann borinn saman við edx sem inniheldur lengd strengsins. Ef að ecx = edx, þá höfum við lokið við að breyta öllum strengnum. Ecx er svo hækkaður um 1 í hverjum hring sem er endurtekin þar til ecx = edx. Þetta má þá setja upp sem for lykkju, sem væri einhvernvegin svona:

for(ecx = 0 ; ecx < edx ; ecx++).



Jæja, þetta er nú orðið heldur langt, svo að ég ætla ekki að skrifa meira í bili. En ég vona að ég hafi náð að koma einhverju til skila og að einhverjir hafi gagn og gaman að. Ef að þessu er vel tekið get ég skellt því inn hvernig maður gerir svo í linux, og hvernig maður gerir gluggaforrit og jafnvel opengl. Ég gæti freistast til að setja inn kóðan af opengl demoinu mínu, en þar meðhöndla ég allar varpanir sjálfur, og væri því nýtt tutorial um hvernig tölvugrafík virkar.

Endilega kíkið síðan á http://www.masmforum.com og kynnið ykkur Assembly forritun. Það gerir mikið gagn fyrir mann að vita hvernig vélin virkar undir húddinu. Ef að maður hefur það í huga hvernig tölvan vinnur þegar maður skrifar kóða í high-level forritunarmáli, þá gerir maður betri kóða en ella.

Þið verðið að afsaka hversu illa myndirnar og kóðarnir komu út, en það er vegna þess að parser-inn á huga.is á eitthvað erfitt með að gera eðlileg bil og tab-s. Endilega spyrjið ef að það er eitthvað sem að vefst fyrir ykkur og ég skal svara einosg ég get.

Takk fyrir mig,
Budingu