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#word18Basically 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.htmhttp://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470160.htmMacro 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.htmExamples of using the API:
http://publib.boulder.ibm.com/infocenter/zos/v1r9/topic/com.ibm.zos.r9.ieaa400/iea2a470182.htmThere 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=20030424140649So 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.