11

so here is the code: (which comes from here, I've also verified this source is in my unix v7 distribution). For reference, this is running on a PDP-11 simulated with the simh program (so please keep in mind the instructions are 16-bit words and values are in octal).

.write = 4.

_write:
    mov r5,-(sp)
    mov sp,r5
    mov 4(r5),r0
    mov 6(r5),0f
    mov 8(r5),0f+2
    sys 0; 9f
    bec 1f
    jmp cerror
1:
    mov (sp)+,r5
    rts pc

.data
9:
    sys .write; 0:..; ..

I get most of what is happening but this gets confusing:

    mov 6(r5),0f
    mov 8(r5),0f+2
    sys 0; 9f

In this old assembly, the first argument is the source and the second arg is the destination. But what destination is 0f? How can we move 06+(r5) into 0f? After this there is mov 8(r5),0f+2 which is even more confusing to me because I thought all numbers were in octal unless they had 0x in front of them.

Finally, there is sys 0; 9f - I've looked and the 0 system call is an 'indirect system call' so I'm guessing it's calling sys .write; 0:..; .. after the 9: label? Does .. have some special meaning in this old kind of assembly code? It looks unusual. After this, bec 1f will skip over the jmp cerror if there is no error bit set, then we return to the caller with rts pc. So, I think I get the bulk of this but if someone could clear up the few mysteries that'd be great.

5
  • 4
    you have bec 1f then a 1 label. Wouldn't 0f be the 0 label? Jul 10, 2020 at 19:32
  • 4
    there is a 0 label, here: sys .write; 0:..; .. Jul 10, 2020 at 19:45
  • 1
    Yes, you are right. I was not aware you could have labels in the middle of lines - I thought they had to begin the line. Jul 10, 2020 at 19:46
  • true, I never saw label declarations in the middle of a line. That's a first for me :) Jul 10, 2020 at 19:47
  • 2
    The semicolon is a "new statement" marker (logical end of line) so ; 0: is the same as having a label at the start of a line. Jul 10, 2020 at 21:08

2 Answers 2

13

Note: this is mostly guesswork

I don't know this assembler but the f suffix seems to denote a label. Example

    bec 1f
    jmp cerror
1:

Those instructions write the parameters in the parameter zone of the system call.

mov 6(r5),0f
mov 8(r5),0f+2

note that there's a 0 label just after the sys instruction. It holds the parameters (copies of those which were pushed on the stack, then "linked" to r5), that follow the system call.

9:
    sys .write; 0:..; ..
                |  |   |
             label |   |
                   |   |
              param1  param2

The 2 .. dots look like placeholders for two 16 bit slots. 0f+2 is just address of label 0, +2 bytes (even if 2 is in octal, it's still 2, BTW)

Those parameters probably have to be right after the sys call because the OS will look at the return address and will fetch the parameter from there. I've seen that a lot with 68000 TRAP instructions, followed by data.

All system calls seem to have been done that way. It is a gateway/bridge code from user code to system code, using traps (which allow to call the OS through vectors, in supervisor mode)

5
  • 6
    Looks right to me ... The whole thing is about numeric labels, which were a (somewhat odd) way of local labels. They could only be one digit, and when using them a specifier of 'f' for forward and 'b' for backward had to be attached to the number. They could be redefined as often as needed and only the nearest going back (or forth) would be used. They were a source of many subtile errors. I think oracle even introduced them to their x86 toolset. So much or quality code ...
    – Raffzahn
    Jul 10, 2020 at 19:51
  • local labels seem to respect the "start with a dot" convention (there are local labels here). But that's as far as my guesswork goes.... and all syscall files have the same labels so you're probably right, those are special local labels... Jul 10, 2020 at 19:56
  • No, this is an AS source, AS is an extreme simple assembler. it does not use a dot convention for local labels. dot can be used for any label.
    – Raffzahn
    Jul 10, 2020 at 20:14
  • I have used this system call. Your guesses are right. Jul 13, 2020 at 6:37
  • that's a classic, now I wonder how it fares in multithreading/reentrant environment if an interrupt happens just before the system call, and the same system call is called from another thread / process with other parameters. Jul 13, 2020 at 7:39
5

Not an answer, as Jean-François Fabre has already deducted all workings, but some hints about the syntax/workings of AS to understand the source.

This source is meant to be assembled using the Unix assember AS. AS is an extreme primitive assembler designed only to handle machine specific parts of Unix. It carries only the most essential functions and those even in a rather ... well, lets be positive and say 'unusual' and special to type way. Some of the features are encountered in above source:

Identifiers and Assignment

To start with, there are identifiers and assignment to them. According to the manual an identifier is:

An identifier consists of a sequence of alphanumeric characters (including period "." underscore "_", and tilde "~" as alphanumeric) of which the first may not be numeric. Only the first eight characters are significant. When a name begins with a tilde, the tilde is discarded and that occurrence of the identifier generates a unique entry in the symbol table which can match no other occurrence of the identifier. This feature is used by the C compiler to place names of local variables in the output symbol table without having to worry about making them unique.

The important implication here is that a period can not only occur at any place but as well at the first without creating any meaning - that is as long as it doesn't equals any legal pseudo-operation (like .global) Thus the definition

.write = 4.

simply creates a symbol that evaluates to the decimal value of 4 - which happens to be the sys call number for write() :)

Local Labels

AS doesn't have local labels, but uses a system of numeric labels that can be redefined. From the manual:

A numeric label consists of a digit 0 to 9 followed by a colon ( : ). Such a label serves to define temporary symbols of the form "nb" and "nf", where n is the digit of the label. As in the case of name labels, a numeric label assigns the current value and type of "." to the temporary symbol. However, several numeric labels with the same digit may be used within the same assembly. References of the form "nf" refer to the first numeric label "n:" :F:orward from the reference; "nb" symbols refer to the first "n:" label :B:ackward from the reference. This sort of temporary label was introduced by Knuth [The Art of Computer Programming, Vol I: Fundamental Algorithms]. Such labels tend to conserve both the symbol table space of the assembler and the inventive powers of the programmer.

So the lines

    mov  6(r5),0f
    mov  8(r5),0f+2

simply refer to the first later instance of the numeric label of '0' - in the second line with an offset of two.

Statements

The second issue one may stumble a bit is the line

    sys  .write; 0:..; ..

Here it's helpful to keep in mind how AS defines statements:

A source program is composed of a sequence of statements. Statements are separated either by new-lines or by semicolons.

So to the assembler this isn't a single statement but multiple, and this already happens before:

    sys 0; 9f

So the assembler sees:

    sys 0;
 9f

The first statement is a sys instruction - which is the system trap instruction used by Unix for kernel calls. While being a two byte instruction, the second byte is of free use, any value can be put here, including zero, as seen. Sys call zero is handled by the kernel as indirect call, so it takes to next word and uses it as address to look for the 'real' call.

So the second statement simply placed the address the label holds as word into memory, enabling the indirection. This is made to avoid self modifying code and allow reentrant programming. The second sys call will not be executed, but simply pared as if it would have been made like encoded.

The sys call placed in the modifiable data segment (.data) is just a data structure formed like a sys call.

    sys  .write
0:..
 ..

So again the first statement assembles as sys, this time using the symbol value (see above) of .write. Write requires the file handle in R0 and a parameter list with two words holding buffer address and length after it.

The next statement defines the local label, referenced in the prior instruction as 0f. A label can be defined here as for the assembler it's a new statement. Any number of whitespaces between the semicolon (;) terminating the previous statement and the label itself can be inserted as labels have to have a terminating colon (:), identifying them. The colon does as well make any following whitespaces optional, thus allowing the 'instruction' to be simply continued.

.. is a special symbol representing the relocation counter (see below), using it alone will reserve space for an address sized data item - much like that 9fbefore. On runtime this will be overwritten with whatever is passed at 6(r5).

The third statement now repeats the reservation of an address sized word, now to be filled with the value at 8(R5).

(We see, this isn't programmed very portable as the size of an address word is hard coded within the move instruction, as two, instead of using a second label. Not really cool, is it?)

Relocation Counter

The relocation counter, accessed as .. is essentially the offset of a segment within memory. by default zero. Like with the location counter (.) it can be assigned, which may be used for very specific address layouts. In this case it's only important that it is handled as an address and will produced as a statement an address sized word. Perfect to reserve space for address parameters, isn't it?

10
  • the size of an address word is hard coded within the instruction instead of using a second label, History has shown this to be a correct decision: the size of a PDP-11 address word has forever remained at 16 bits ! Jul 10, 2020 at 21:11
  • 1
    @another-dave True, but that's pure luck, as noone can see the future. But that's in part beside the point which is about making as least assumptions as possible. By simply adding a second label like sys .write; 0:..; 1:.. and using mov 8(r5),1finstead, it would not only be better to read (code is documentation) but as well all responsibility about address calculation handed over to the Assember. That's where it belongs. Doesn't it?
    – Raffzahn
    Jul 10, 2020 at 21:25
  • I think that's a matter of opinion. I certainly had no problems with knowing that 4(SP) is two words under the top of stack, and that's the most convenient way to write it. Coding the displacement as 'FOO(SP)' with FOO defined elsewhere as 4 is cumbersome - for small values of FOO, of course. Coupled with instructions like MOV (R5)+,somewhere, where R5 is architecturally sure to be incremented by 2, it's pretty much the case that '2' is seared into the brain of a PDP-11 programmer as well as the PDP-11 hardware. This is not necessarily valid for other computers. Jul 10, 2020 at 23:05
  • In this specific case, it appears the convention might be that the label is at the start of the argument list, and then the n'th argument is known to be at offset n*2. No benefit in labelling individual argument slots. Jul 10, 2020 at 23:19
  • 1
    @LainIwakura it's AS. AS is a bad hack. It does read 8d as 10o and 9d as 11o. only for multi digit numbers it needs a trailing dot. 8(r5) is simply register 5 with offset 8d or 10o
    – Raffzahn
    Jul 11, 2020 at 17:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .