3

I am creating a very low-level application, and I have the bootloader ready. I essentially have to rebuild the scanf() function, as I cannot use any libraries in the project. What would be a good, basic application in Assembly that can read input? I can handle reading it into a C char[] or whatever other data type I want, I just need to figure out how to read input without any system calls.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • Input from what? – ThingyWotsit Apr 27 '17 at 21:36
  • Keyboard input; I will edit my post and clarify. –  Apr 27 '17 at 21:37
  • so, call BIOS? (like INT 16h?) – Sedat Kapanoglu Apr 27 '17 at 21:38
  • Yes, those calls would be acceptable. –  Apr 27 '17 at 21:39
  • You need to go and do some research here. In my opinion, this is too broad. There are plenty of examples, both here and easily found on the Internet, demonstrating how to accept input using BIOS calls. If you're looking for an assembly tutorial, this is also not the place to ask for that. – David Hoelzer Apr 27 '17 at 22:27
  • Rebuild printf or scanf? I assume you meant the latter since most of your question is about reading input. You mention UEFI. Is this UEFI or legacy bootloading? (legacy bootloading usually involves an entry point of 0x7c00.) – Michael Petch Apr 27 '17 at 22:53
  • Back in the old days, this was a must-have reference for BIOS programming using assembly language: Peter Norton: Inside the IBM PC. It thoroughly explains all the functionality available at the BIOS level. The book is quite old, but I'm sure there are electronic versions available. – BlueStrat Apr 27 '17 at 23:51
  • 2
    @BlueStrat : the modern day encyclopedia of DOS and BIOS interrupts is [Ralph Brown's Interrupt List](http://www.ctyme.com/rbrown.htm) – Michael Petch Apr 27 '17 at 23:53
  • 1
    @MichaelPetch, thanks for the update, guess it shows I'm officially a full-grown greybeard ;) – BlueStrat Apr 27 '17 at 23:56
  • 1
    I'm somewhat old (approaching 50) Back in the day Waite Group book, Norton's guide, and PC Intern were good books. Still have them on my shelves along with Dr. Dobbs Journal and Byte Magazine. – Michael Petch Apr 27 '17 at 23:58
  • I'm only 28 and have copies of Norton's *Inside the IBM PC* and *Programmer's Guide to the IBM PC & PS/2*. I also have a beard, but it isn't grey yet. :-) That said, I've no idea how to answer this question. You say, *"I just need to figure out how to read input without any system calls."* Well, that is impossible. You will need to make system calls, because system calls are how you obtain input from the keyboard. What else would there be? You want to read raw input from the keyboard buffer? I don't recommend that, and what would be the point? Do you just mean no *operating system* functions? – Cody Gray - on strike Apr 28 '17 at 10:01
  • Related: [How can I get user input in my assembly bootloader?](https://stackoverflow.com/q/53882814) for programming in asm, with a short example. – Peter Cordes Oct 13 '22 at 04:15

1 Answers1

3

It appears you are writing code for real mode. You don't say which C compiler you are using, but your own answer suggests you are in fact in real mode. I recommend WATCOM's C compiler as it can generate true 16-bit code. If you are using GCC with the -m16 option I don't recommend it for 16-bit code. I have another Stackoverflow answer that discusses some GCC issues.

The best source of DOS and BIOS interrupt information is Ralph Brown's Interrupt List. The information about getting a single keystroke via Int 16h/AH=00 is:

KEYBOARD - GET KEYSTROKE

AH = 00h

Return:

AH = BIOS scan code
AL = ASCII character

This BIOS function won't echo characters so another useful BIOS function is Int 10h/AH=0eh is displaying a single character to the terminal:

VIDEO - TELETYPE OUTPUT

AH = 0Eh
AL = character to write
BH = page number
BL = foreground color (graphics modes only)

Return:

Nothing

Desc: Display a character on the screen, advancing the cursor and scrolling the screen as necessary

To print a character in text mode you can place BX into 0, the character to print in AL and call the interrupt.


Using the information above you can write some simple wrappers around both BIOS interrupts using inline assembly. In GCC you can use Extended Inline Assembly templates. They could look like this:

#include <stdint.h>

static inline void
printch (char outchar, uint16_t attr)
{
   __asm__ ("int $0x10\n\t"
            :
            : "a"((0x0e << 8) | outchar),
              "b"(attr));
}

static inline char
getch (void)
{
   uint16_t inchar;

   __asm__ __volatile__ ("int $0x16\n\t"
            : "=a"(inchar)
            : "0"(0x0));

   return ((char)inchar);
}

In Watcom C you can create functions with #pragma aux:

#include <stdint.h>

void printch(char c, uint16_t pageattr);
char getch(void);

#pragma aux printch = \
    "mov ah, 0x0e" \
    "int 0x10" \
     parm [al] [bx] nomemory \
     modify [ah] nomemory

#pragma aux getch = \
    "xor ah, ah" \
    "int 0x16" \
     parm [] nomemory \
     modify [ah] nomemory \
     value [al]

With these basic functions you only need to write a function that gets a string from the user, echoes the characters back as they are input, and stores them in a buffer. The ASCII character getch returns for a newline is the carriage return \r (0x0d). When we reach the maximum number of characters requested or a newline is encountered we stop and NUL terminate the string. Such a function could look like:

/* Get string up to maxchars. NUL terminate string.
   Ensure inbuf has enough space for NUL.
   Carriage return is stripped from string.
   Return a pointer to the buffer passed in.
*/
char *getstr_echo(char *inbuf, int maxchars)
{
    char *bufptr = inbuf;

    while(bufptr < (inbuf + maxchars) && (*bufptr = getch()) != '\r')
        printch(*bufptr++, 0);

    *bufptr = '\0';
    return inbuf;
}

If you don't wish to use inline assembly you can create an assembly module with getch and printch done in pure assembly. This is less efficient than the code that can be generated with inline assembly but it is far less error prone.


The getstr_echo function is not feature complete and is meant to be used as a starting point for your own code. It doesn't properly handle things like backspace.

Community
  • 1
  • 1
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • The only problem with your GCC example is that I cannot use any libraries. That's why I am creating a scanf and printf from scratch. Other than that, these are great examples, which I will learn from. Thank you. –  May 03 '17 at 23:01
  • @LoganJamison : If you are referring to `stdint.h` it is separate from the standard library headers (and can be used in free standing code like your OS). `stdint` defines things like uintptr_t and uint16_t etc. If you don't want to use `stdint` and you are generating a 32-bit kernel then you can replace `uint16_t` with `unsigned short int` in the code. Both are 16-bit unsigned values. – Michael Petch May 03 '17 at 23:04
  • 1
    Alright, thank you very much. I am hardly familiar with most C libraries and expected that I wouldn't be using any for this project. –  May 03 '17 at 23:08