OSDev.org

The Place to Start for Operating System Developers
It is currently Fri Aug 07, 2020 9:38 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: How do I execute the OUT instruction in assembly?
PostPosted: Sat Dec 28, 2019 8:40 pm 
Offline

Joined: Tue Aug 26, 2014 5:07 pm
Posts: 14
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:
.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:
#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:
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.
https://forum.osdev.org/viewtopic.php?f=1&t=15381&p=110436&hilit=outb+implementation#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?


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Sat Dec 28, 2019 9:00 pm 
Offline

Joined: Fri Oct 04, 2019 10:10 am
Posts: 18
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.


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Sun Dec 29, 2019 11:41 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1637
Location: Athens, GA, USA
peachsmith wrote:
And just for reference, this is the inline assembly implementation from the wiki:
Code:
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.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
μή εἶναι βασιλικήν ἀτραπόν ἐπί γεωμετρίαν
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Sun Dec 29, 2019 3:10 pm 
Offline

Joined: Tue Aug 26, 2014 5:07 pm
Posts: 14
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:
.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.


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Mon Dec 30, 2019 10:57 am 
Online
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 2169
peachsmith wrote:
I'm trying to implement the outb functionality without resorting to inline assembly.

Why?


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Sat Jan 04, 2020 11:26 pm 
Offline

Joined: Tue Aug 26, 2014 5:07 pm
Posts: 14
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.


Top
 Profile  
 
 Post subject: Re: How do I execute the OUT instruction in assembly?
PostPosted: Mon Jan 06, 2020 5:56 pm 
Online
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 2169
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:
extern void __attribute__((regparm(2))) my_outb(uint16_t port, uint8_t value);
Code:
my_outb:
   xchgl %eax, %edx
   out %al, %dx
   ret


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot] and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group