Page 1 of 1

[SOLVED] How do I execute the OUT instruction in assembly?

Posted: Sat Dec 28, 2019 8:40 pm
by peachsmith
I'm trying to implement the outb functionality without resorting to inline assembly.
Currently, I'm using the bare bones example from the wiki to test this by enabling the cursor, setting its position, and writing "Hello, World!" to the screen.
When using the outb funtion from the wiki, the text "Hello, World!" appears and the cursor is placed in the expected location.
When I call my assembly procedure to do outb, the kernel appears to hang.
I suspect this is due to me misunderstanding the i-386 System V ABI.

Here is my assembly procedure that attempts to use outb:

Code: Select all

.section text

.global my_outb

# This procedure executes the outb instruction.
my_outb:
	
	# According to the x86 System V ABI found at
	# http://www.sco.com/developers/devspecs/abi386-4.pdf
	# The first argument is located at offset 8(%ebp) and the
	# second argument is at offset 12(%ebp).
	# Since the C function signature is
	# extern void my_outb(uint16_t port, uint8_t value)
	# the port number should be at 8(%ebp) and the value should be at 12(%ebp).	
	movw 8(%ebp), %dx  # first argument (a word containing the port number)
	movb 12(%ebp), %al # second argument (a byte containing the value)
	outb %al, %dx      # write the byte in al to the port in dx
It is exposed to C code with the following header:

Code: Select all

#ifndef PORT_H
#define PORT_H

extern void my_outb(uint16_t port, uint8_t value);

#endif
And just for reference, this is the inline assembly implementation from the wiki:

Code: Select all

static inline void inline_outb(uint16_t port, uint8_t val)
{
    asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
    /* There's an outb %al, $imm8  encoding, for compile-time constant port numbers that fit in 8b.  (N constraint).
     * Wider immediate constants would be truncated at assemble-time (e.g. "i" constraint).
     * The  outb  %al, %dx  encoding is the only option for all other cases.
     * %1 expands to %dx because  port  is a uint16_t.  %w1 could be used if we had the port number a wider C type */
}
Combuster mentioned in another post that he had an implementation available, but the link appears to be broken.
viewtopic.php?f=1&t=15381&p=110436&hili ... on#p110436

I looked at the assembly generated for the inline example during compilation and tried to copy that in my implementation, but to no avail.
What am I missing here?

Re: How do I execute the OUT instruction in assembly?

Posted: Sat Dec 28, 2019 9:00 pm
by reapersms
You are forgetting the function prolog and epilog.

To use it as is, change ebp to esp in the moves, and subtract 4 from your offsets, and add a ret after.

To obey the calling convention and use the code you have, add push ebp / mov esp, ebp before, and pop epb / ret after.

This assumes you are in 32 bit more, using at&t syntax, etc.

Re: How do I execute the OUT instruction in assembly?

Posted: Sun Dec 29, 2019 11:41 am
by Schol-R-LEA
peachsmith wrote:And just for reference, this is the inline assembly implementation from the wiki:

Code: Select all

static inline void inline_outb(uint16_t port, uint8_t val)
{
    asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
    /* There's an outb %al, $imm8  encoding, for compile-time constant port numbers that fit in 8b.  (N constraint).
     * Wider immediate constants would be truncated at assemble-time (e.g. "i" constraint).
     * The  outb  %al, %dx  encoding is the only option for all other cases.
     * %1 expands to %dx because  port  is a uint16_t.  %w1 could be used if we had the port number a wider C type */
}
I looked at the assembly generated for the inline example during compilation and tried to copy that in my implementation, but to no avail.
What am I missing here?
One thing you may be missing is the inline modifier on the wiki's version, possibly because the word 'inline' is overloaded in this usage. This is one of those things which, even if you know how it works (which I expect you do), can be easily overlooked in practice when you aren't expecting it.

For those who aren't aware (which may or may not include the OP), in an inline function, no callable separate function is generated by the compiler; rather, the function body (which in this case is entirely inline assembly, but it would be just as true for an inline function with no inline assembly in it at all) is inserted directly into the caller's code stream. No function prolog or epilog are generated, no CALL or RET instructions are used (or their equivalents on other ISAs), and arguments are interpolated directly rather passed on the stack.

In some ways, an inline function is more like a macro than a separate function in the usual sense, though unlike a macro the code might be wholly or partially compiled first (depending on the compiler and the code), rather than being added with a simple string interpolation, and they have all the type checking and other safeguards found in ordinary functions. Indeed, when the standards committee added the inline keyword to C (I don't recall which standard revision this was, though IIRC, it had been in C++ since the outset, so it very likely was ANSI C in 1988), it was mainly proposed as a safer alternative to the existing pre-processor macros.

Since they are mainly an efficiency tweak, inline functions are usually only used for small sections of code where the overhead of an ordinary function's prolog/epilog and stack handling would dominate the code generated for the function. Usual practice is to use them sparingly, even for smaller functions, as they do increase the code size of the functions they are 'called' by, and repeated use of even small inline functions can end up being less space efficient than a full-fledged function, regardless of the calling overhead.

In this case, the desired code is a single assembly instruction, which is the ideal situation for using an inline function containing inline assembly code. While I am no fan of inline assembly for general use in C, this is one instance where it really makes sense.

I don't recall off-hand if the C standard considers the inline directive a modifier or a hint, but I am pretty sure that the compiler can ignore it if the optimizer finds that it is less efficient inlined than called. However, this is something of an edge case, regardless. I do know that some compilers require shared inline functions to be defined in the shared header rather than in one of the source files (one of the few exceptions to the rule of never having a function's implementation inside a header), but I'm not sure if that is actually required by the standard. Again, this isn't necessarily relevant here.

Re: How do I execute the OUT instruction in assembly?

Posted: Sun Dec 29, 2019 3:10 pm
by peachsmith
Well, this is embarrassing.
I was using ".section text" instead of ".section .text".
So even when I was using the prologue and epilogue, I still wasn't getting the expected behavior.
Here is my updated implementation of outb:

Code: Select all

.section .text         # corrected the section name

.global my_outb

my_outb:
	
	pushl %ebp
	movl %esp, %ebp
	subl $8, %esp
	
	movw 8(%ebp), %dx  # first argument (a word containing the port number)
	movb 12(%ebp), %al # second argument (a byte containing the value)

	out %al, %dx       # write the byte in al to the port in dx
	
	leave
	ret
Thanks for your time.

Re: How do I execute the OUT instruction in assembly?

Posted: Mon Dec 30, 2019 10:57 am
by Octocontrabass
peachsmith wrote:I'm trying to implement the outb functionality without resorting to inline assembly.
Why?

Re: How do I execute the OUT instruction in assembly?

Posted: Sat Jan 04, 2020 11:26 pm
by peachsmith
Octocontrabass wrote:
peachsmith wrote:I'm trying to implement the outb functionality without resorting to inline assembly.
Why?
The primary reason was to try to avoid all this compiler optimization goofiness that I hear so much about.

Re: How do I execute the OUT instruction in assembly?

Posted: Mon Jan 06, 2020 5:56 pm
by Octocontrabass
I suppose that's a fair reason.

Personally, I'd rather use inline assembly in order to take full advantage of optimizations. (Perhaps I spend too much time on compiler optimizations and not enough time writing code...)

I do have one suggestion, though: tell the compiler to put your parameters in registers.

Code: Select all

extern void __attribute__((regparm(2))) my_outb(uint16_t port, uint8_t value);

Code: Select all

my_outb:
	xchgl %eax, %edx
	out %al, %dx
	ret