Tips and tricks for programming in a programming language

I am going to write my own toy OS, and at the moment I do it mainly in the assembly (NASM) - partly because I hope this helps me figure out the disassembly of x86, and also because I am also very interested!

This is my first programming experience in the assembly - I assemble things faster than I expected, however, as with learning any essentially different language, I find that my code is structured quite randomly when I try to figure out what patterns and conventions I have to use.

Currently, in particular, I am struggling with:

Register tracking

At the moment, everything is in 16-bit mode, and therefore I have only 6 general-purpose registers that I can work with, even with fewer used to access memory. I continue to trample on my own registers, which in turn means that I often change registers to avoid this, so it's hard for me to keep track of which registers contain which values, even with liberal comments. This is normal? Is there anything I can do to make tracking easier?

For example, I started commenting on all my functions with a list of lists that are down:

; ================ ; c_lba_chs ; Converts logical block addressing to Cylinder / Head / Selector ; ax (input, clobbered) - LBA ; ch (output) - Track number (cylinder) ; cl (output) - Sector number ; dh (output) - Head number ; ================ 

Stack tracking

On several occasions, I started using the stack when I ran out of registers, but it makes things a lot worse - something more complicated than just a push call pop sequence to save registers makes me lose track completely, which makes it difficult to even say there are whether I have the correct number of elements on the stack (especially when it comes to error handling - see below), not to mention the order in which they are located. I know that there must be a better way to use the stack, I just don’t see what it is.

Error processing

I used the carry flag and the zero flag (depending on the function) to indicate the error to the caller, for example:

 myfn: ; Do things jz .error ; Do more things ret .error: stc ret 

Is this the usual way to indicate errors?

Also, are there any other tips or tricks that I can use to better build the assembly?

Finally, are there any good resources / examples of a well-written build? I came across "The Art of Assembler Programming," however it seems to focus very much on learning the language with less attention to how to structure the code. (Also some code examples use segments, which I think I should avoid).

I do all this using zero segments (flat memory model) to simplify the task and make the task easier if / when I start using C.

+6
source share
1 answer

Don’t worry, you are pretty much on the right track. As an assembly, you can do what you want, so you have the freedom to decide how you want to manage your registers and data. I would recommend developing some standards for myself, and using C-like standards might not be a bad idea. I would also recommend using a different assembler language for the first such project (for example, ARM running on qemu), x86 is somewhat terrible as command sets go. but this is a separate issue ...

Assemblers usually allow you to declare variables, if you wish, a memory with names:

 bob: .word 0x1234 

Then from assembler (using ARM asm here)

 ldr r0,bob add r0,#1 str r0,bob 

Registers are used temporarily, real data is stored in memory. Such a model can help track everything, since real data is stored in memory with user-created names, like a high-level language. x86 makes this even easier, since you can perform memory operations and do not have to go through all the registers. Similarly, you can control this using the stack frame for local variables, subtract a certain amount from the stack to cover your stack stack for this function, and inside this function know / remember that the joe variable is the +4 stack pointer, and ted is the pointer stack +8, etc. Probably use comments in your code to remember where these things are. Remembering to restore the pointer / frame of the stack to its entry point before returning. This method is a bit more complicated since you do not use variable names other than numerical offsets. But it provides local variables and recursion and / or some memory savings.

Performing this work as a person with eyes and hands (keyboard and mouse), you probably want to save the data in the register no more than what can fit on the screen in a text editor at a time, at a glance to see the variable goes into the register, and then returns to the variable in memory at a glance. A program / compiler, of course, can track as much memory as there is in a system, much more than a person. That is why compilers, on average, generate better assembler than people (in specific cases, people can always tweak or fix the problem).

Error handling, you need to be careful using flags, for some reason this doesn't seem right to me. This can be very good, interrupts save flags, your code will need to save or set flags, etc. Hmm, a problem with flags - you need to check / use this return value right after the function returns, before you have an instruction that changes flags. where, if you use a register, you can not change this return register for many instructions before you need to try or use this return value.

I think the bottom line is here, look at the C-convention rules that compilers use for this set of commands, and perhaps other sets of instructions, you will see strong similarities for a good reason. They are manageable. With so many registers, you can understand why calling conventions sometimes go straight to the stack for all arguments, and sometimes return values. I was told that bios Amiga has a user calling convention for every bios function that was created for a tough and fast runtime system, but the serum tries to recreate the bios in C using compilers and attaching functions to them using the assembler shell is at best more difficult. I am sure that without good documentation for each function, this is unmanageable. On the way, you can decide if you want this portable machine and you might want you to choose a generally accepted calling agreement. You still want to comment on your code to say that parameter 1 is this, and parameter 2 is this, etc. On the other hand, if you currently or earlier had a programmed x86 assembler that calls DOS and BIOS calls, you would be quite comfortable looking at each function in the link and putting the data in the appropriate registers for each function. Since there were good reference materials, it was convenient to have every functional function.

+5
source

Source: https://habr.com/ru/post/887722/


All Articles