Author Topic: All gprs available for use?  (Read 22616 times)

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
All gprs available for use?
« on: June 20, 2011, 07:44:48 AM »
I remain confused about what registers we can use and what registers we shouldn't use. Out of the "normal" 32 bit gprs are these all available for program use? If I use esp for general use will I screw up my stack somehow? I am used to having alot of registers available with no dedicated purpose but on x86 it seems like even though they are called general purpose registers they almost all have some dedicated purpose. I have read some of these registers are implied in certain opcodes but  aside from that I do not understand if there are any other restrictions. Thanks.
« Last Edit: June 20, 2011, 07:47:32 AM by JoeCoder »
If you can't code it in assembly, it can't be coded!

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: All gprs available for use?
« Reply #1 on: June 20, 2011, 11:24:10 AM »
Certain registers are indeed used for certain operations ( for example, edi/esi used with string ops ).  The gprs your program is free to use to your hearts content are: eax, ecx, edx.  Yup, in 32-bit environments that's it.  Now, the main reason for this restriction is due to calling conventions.  In other words, if you make a call to an operating system function ( or any other library function for that matter ) you want to be assured that your setup is not trashed.  The ebx/esi/edi registers must be preserved across function calls ( non-volatile ) and, provided you save/restore it in your function prologue/epilogue you are the free to use them as well.

Normally, esp/ebp are used for stack frame to reserve local variable space ( esp ) and access function parameters.  If your function is small you can easily prefactor offsets and use esp only.  Thus you can save ( push ) ebp and now use it as well as the gprs. 

Unless you are writing an OS or device driver I highly recommend not modifying the cs/ds/es/fs/gs segment registers.  Most protected mode systems won't let you change them anyways.

You have complete access to the FPU stack st(0)-st(7) for doing floating point operations.

In 64-bit mode a whole slew of new registers are at your disposal.  However, again you will need to be concerned with the calling convention of your OS of choice in order to know which registers need preserved across function calls.

To see how this gets implemented on your OS simply use your favorite C compiler and create a small program with a few functions that make calls to the operating system.  Compile the program and enable assembly source output.  Examine that output to see the conventions in action.

Shameless plug: Even better check out our NASMX project http://www.asmcommunity.net/projects/nasmx/ for complete macros,demos, and source that will help you with many of these issues.

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
Re: All gprs available for use?
« Reply #2 on: June 20, 2011, 12:11:36 PM »
Hi Rob. I am still lost. In the Duntemann book on Linux with NASM he routinely uses EBP and ESI in his small examples. Is he disrupting the stack frame and it just doesn't matter because of the tiny examples? He does call system calls like read, write etc. in these small programs. They seem to work as in they produce the desired result. I do not know if they do something bad that isn't detected.

Are you saying only eax, ecx, and edx should be expected to be preserved across syscalls (I'll ask about Linux since that is where I'm working right now)? Is there any problem if I use the other 5 "gprs" in a top-level program? Are you saying they should not be used, or they should not be expected to survive syscalls, library calls etc.? Or something else. Sorry for my density here.

I did download nasmx but I haven't looked at it yet because I don't have the basics down. I am trying to write a program to process a text file but I am all tangled up because I am not used to character oriented i/o and don't know how to handle things that should be processed as lines. And I can't figure out how to pass arguments to procedures and return answers. I need to understand the calling and return conventions in this environment, I don't think it's covered in the Duntemann book. Do you know a book that explains it better? I realize when I try to learn a new language or system I always try to understand the structure of it before I understand the details of how to do things at the intruction level. If I can understand how things relate and talk to each other I can go on to the details. If not I have a hard time getting anything done. One thing is for sure, you guys who learned x86 would love the systems I work on. Things are so much simpler and easier!

BTW thanks for the suggestion on compiling a small program. I know it's a good idea and I will do it soon but at this point it will probably confuse me more because of the as(inine) syntax of gas and also I have written about 100 lines of C in my life (not a PC programmer). My favorite C compiler is no C compiler  :D
« Last Edit: June 20, 2011, 12:28:47 PM by JoeCoder »
If you can't code it in assembly, it can't be coded!

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
Re: All gprs available for use?
« Reply #3 on: June 20, 2011, 01:20:36 PM »
Let me ask more specificially maybe that will illustrate my total lack of understanding of x86 etc.

I want to write a filter program for Linux that will read from stdin and write to stdout. The actual syscalls to read and write and exit are *about* all that I really understand  :D

Since I want to process things from a logical view of a line of text I finally realized if I try to read and write inline I will make a mess of my code and have alot of duplication since I will have to constantly test for the end of the input and output buffers, etc. so I decided to make procedures (maybe not formally since I seem to abuse definitions without trying) but something I can call for reading a character and writing a character kind of like getch and putch (I think.) call..ret Those procedures should encapsulate everything necessary to allow the mainline to just worry about processing one input or one output character while all the logic of reading and writing and dealing with the buffers stays inside the procedures.

In the mainline I want to call the getch routine. The first time I call it it should figure out there's no data in the buffer and read a buffers worth of data and return (how?) the address of the current byte in the input buffer. The first time this happens is a special case, guaranteed to be the beginning of a line of text. If this character is a percent sign then the line is a comment line. I should continue getting characters until I hit a ff char (0ah) and that is the end of a line. All this time nothing happens with the output. If I hit eof while trying to find the end of the line I check if there's any output in the output buffer and write it, and I'm done.

If the first character of a line isn't a percent sign then I should copy from input to output one character at a time until I either copy a ff char or run off the input buffer or hit eof on the input file. In the process if I fill the output buffer I should write the output buffer and start the output pointer again at the first position in the output buffer.

Most of my questions have to do with how arguments should be passed to and fro. Should the mainline be operating on this data with addresses in registers, or should I be saving the addresses in fields in the data segment? Do I pass parms to the getch and putch routines in registers or on the stack or in data fields? How do I return values from those routines to the mainline? How do I return success or failure? I don't like the system of returning 00 for eof or an error because 0h is also a valid character. I have always separated return codes from arguments, but that is standard in my work environment and may not be on x86. I was thinking of testing eax on a read for example with or eax,eax and then letting the mainline do a jz eof, I saw most of the other opcodes I need to use in the getch routine seem not to affect rflags, which helps in my case.
« Last Edit: June 20, 2011, 01:27:46 PM by JoeCoder »
If you can't code it in assembly, it can't be coded!

Offline Keith Kanios

  • Full Member
  • **
  • Posts: 383
  • Country: us
    • Personal Homepage
Re: All gprs available for use?
« Reply #4 on: June 20, 2011, 05:01:19 PM »

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
Re: All gprs available for use?
« Reply #5 on: June 20, 2011, 05:24:12 PM »
Thanks! You always seem to know where to find the good doc. Most of this is making my eyes roll but I believe most of what I am asking in this thread is in that doc. You da man!  ;)
If you can't code it in assembly, it can't be coded!

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: All gprs available for use?
« Reply #6 on: June 20, 2011, 05:26:03 PM »
Read Agner Fog's Calling Conventions guide.

As Keith correctly points out ( and you correctly surmise) you need to understand the conventions better.  We could fill up dozens of posts talking about this one issue alone.  Agner's site has some of the best info and tools available.  I too highly recommend the whole site http://www.agner.org/optimize/.
After doing some heavy reading and research you will be better equipped to ask more specific questions.  We'll be here awaiting your progress.  ;)

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: All gprs available for use?
« Reply #7 on: June 20, 2011, 07:57:17 PM »
Hi Joe (Is that actually your name? Can I call you "Joe"?),

This all depends a lot on your "viewpoint". If you're interfacing with C (or other "library" code), you need to worry about "conventions". If you're not, you have much more flexibility! The CPU doesn't care about "conventions", but some instructions have "implicit" operands, as you know. A complete list of these would get quite long!

The one that's most likely to get you in trouble is esp. It points to your stack, and is an implicit operand to "push(a)", "pop(a)", and "call" and "ret" (these last two are not always obvious to beginners!). In theory, it can be used as "general purpose" - you'd want to save and restore it, but not with "push" and "pop", obviously - in a .data variable. Best bet is probably to consider esp as "reserved" for stack operations.

A bug that often bites beginners is the implicit operands to "div". Assuming a 32-bit operation, "div" divides edx:eax (edx * 4G + eax) by some operand we provide (32-bit reg or memory location), and returns quotient in eax and remainder in edx. If the result won't fit in eax (if edx >= to the operand we provide), it causes an exception. This is often reported as "divide by zero" (dos) or "floating point exception" (Linux) - which are pretty confusing! We probably only care about what's in eax, but the CPU cares about edx too. This comes up in "convert number to ascii" routines. We want to explictly zero edx - or the "cdq" instruction sign-extends eax into edx (implicit operands!) for signed numbers - immediately before the "div"!

I suppose sys_calls are a "convention" all their own. As you know, we put parameters in ebx, ecx, edx, esi, edi, and (for newer sys_calls) ebp... as many as necessary. The result or error code is returned in eax. The other registers are not altered (I'm not sure if this is "documented", but it seems to be the case) - including ecx and edx, which C is "allowed" to trash.

There's a whole lot more to this story, which I haven't got time/ambition to get into now. A friend just showed up, so...

Later,
Frank


Offline Keith Kanios

  • Full Member
  • **
  • Posts: 383
  • Country: us
    • Personal Homepage
Re: All gprs available for use?
« Reply #8 on: June 20, 2011, 10:39:20 PM »
Thanks!

NP. The other term to use in searching for this material, is Application Binary Interface.

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
Re: All gprs available for use?
« Reply #9 on: June 21, 2011, 09:05:31 AM »
Hi Joe (Is that actually your name? Can I call you "Joe"?),

Yes and yes. Don't think people didn't bust my chops in comp sci classes over the Joe Coder thing btw  ;)

This all depends a lot on your "viewpoint". If you're interfacing with C (or other "library" code), you need to worry about "conventions". If you're not, you have much more flexibility! The CPU doesn't care about "conventions", but some instructions have "implicit" operands, as you know. A complete list of these would get quite long!

Ok that sounds good so far. I just don't understand why it should be so complicated even with library calls. I am used to having one standard interface and one set of linkage conventions in all cases. There are minor variations on it but nothing that affects the actual calling sequence. Did you mean to say a complete list would be very long even in one os/platform scenario or just across os/platform. If the latter then that is reasonable. Or maybe you meant the implied register use for instructions...at least that's documented in one place. I have no issue with that. I have a hard time with stuff that is essential but not documented in one place by the guys who should have documented it. It's great that people know stuff and Agner Fog writes that paper, but why didn't the guys who wrote Linux and libc or whatever document all that? If they did, then how do we find the doc?

Here's a page that describes the MVS calling sequence. There are a few variations on it but this basic approach works in 99% of all cases.

http://publib.boulder.ibm.com/infocenter/zos/v1r11/topic/com.ibm.zos.r11.ieaa600/nochange.htm#word18

Basically you don't have to learn new ways to call any code in MVS and you never have to worry about some called code trashing your environment because every IBM service, aside from using the standard linkage conventions also publishes the complete API description for every call so you know what input regs have to contain, what regs are used and not restored (and that's usually but not always standard and restricted to a small subset) what output registers will contain, and tons more environmental info. It's a really nice development environment.

Here is an example of some of the documentation they provide for one specific API used to allocate and release virtual storage:

Input and Output register usage:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470159.htm
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470160.htm

Macro parameters for calling the API:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470168.htm

Possible abend codes:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470169.htm

Return codes:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470170.htm

Examples of using the API:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470182.htm

There is more to the doc than this so I just posted a few representative links for one commonly used API.

The one that's most likely to get you in trouble is esp. It points to your stack, and is an implicit operand to "push(a)", "pop(a)", and "call" and "ret" (these last two are not always obvious to beginners!). In theory, it can be used as "general purpose" - you'd want to save and restore it, but not with "push" and "pop", obviously - in a .data variable. Best bet is probably to consider esp as "reserved" for stack operations.

Ok and thanks for saving me a test. I was just about to push an integer onto the stack, blast esp and then pop and see what I get back. I will probably do it anyway just for fun.

A bug that often bites beginners is the implicit operands to "div". Assuming a 32-bit operation, "div" divides edx:eax (edx * 4G + eax) by some operand we provide (32-bit reg or memory location), and returns quotient in eax and remainder in edx. If the result won't fit in eax (if edx >= to the operand we provide), it causes an exception. This is often reported as "divide by zero" (dos) or "floating point exception" (Linux) - which are pretty confusing! We probably only care about what's in eax, but the CPU cares about edx too. This comes up in "convert number to ascii" routines. We want to explictly zero edx - or the "cdq" instruction sign-extends eax into edx (implicit operands!) for signed numbers - immediately before the "div"!

Thanks for the tip. The idea of implicit operands is something we have in the ISA I work with also. As you say there needs to be some way to get quotient and remainder on integer divides and I believe after seeing what you wrote this was implemented similarly to the way it was always done on MVS but I can't remember off the top of my head whether we have to zero the high order register or whether it's defined to be zero before the divide (you can tell I don't do a lot of math stuff but I think we have to clear it). Also when you multiply you have the potential to get a number as wide as the bits in multiplier + bits in multiplicand. They have to go somewhere.

Here's some trivia with the explanation of one of the divide instructions for MVS http://publibfp.boulder.ibm.com/cgi-bin/bookmgr/BOOKS/dz9zr002/7.5.48?DT=20030424140649

So yeah we have to zero the high order register if we are dividing a 32 bit number by a 32 bit number.

I suppose sys_calls are a "convention" all their own. As you know, we put parameters in ebx, ecx, edx, esi, edi, and (for newer sys_calls) ebp... as many as necessary. The result or error code is returned in eax. The other registers are not altered (I'm not sure if this is "documented", but it seems to be the case) - including ecx and edx, which C is "allowed" to trash.

That stuff doesn't bother me since I found a clear definition for it in several places on the net and I guess if I needed any of the input to be preserved I would just check the results of the particular syscall in normal and error cases and hope I understood the behavior...or would just push what I needed onto the stack or save it in program storage and restore it after the call.

My specific question was about trying to understand how I should do linkage with my own local procedures was based on me not having enough (for my taste) registers and not knowing what they were for. From what you wrote I understand as long as I don't screw around with esp I should be able to use the other 7 without hurting myself, but if I call an external piece of code I need to know what's preserved or not (obviously aside from what that code expects to receive/return) for my own health. Rob seemed to allude to that with his 3 and 3 in the answer above. If so, I can live with that I guess. I'll try a few things and see how they work. Man it's tough learning to think in assembly on a totally different OS and totally different hardware than what we are used to. Makes me think about the discussion a few of us had whether assembly is harder than HLL or not and I said I think it's easier but just requires thinking in small steps. But that's just on one OS on one piece of hardware. It's alot easier to be a HLL programmer across a few different OS and platforms than it is to write assembly! Not only the instructions and architecture are different, but the whole way you have to approach the problem is completely different because it's so tied into what you are coding on. In HLL that issue never comes up. I used to think everybody should learn assembly or they really had no idea what they were doing. Now I am starting to think everybody should learn assembly on more than one machine and OS or they really have no idea what they were doing! It's a real eye opener.

The stack is convenient to have and easy to use but I now realize (since I don't have one for my work) it also has a significant drawback of once you pop something off you can't access it again unless you save it elsewhere and there is no direct access to the stack (although I guess you can offset from esp) in an arbitrary state since things could get pushed or popped in a multithreaded case. The way we do program linkage every program provides a unique savearea for use by the called program, and the called program is responsible for saving and restoring the caller's registers. So at various points in processing both the caller and the callee have a designated area to pull register contents out of, and that makes it much easier to avoid use of working storage fields or other hackish ways of preserving data you need to access a few times. Anyway thanks to everybody for all the help! I figure I'll get you guys interested in MVS sooner or later while you help me with x86.
« Last Edit: June 21, 2011, 12:21:04 PM by JoeCoder »
If you can't code it in assembly, it can't be coded!

Offline JoeCoder

  • Jr. Member
  • *
  • Posts: 72
  • Country: by
Re: All gprs available for use?
« Reply #10 on: June 21, 2011, 09:06:09 AM »
If you can't code it in assembly, it can't be coded!