5
\$\begingroup\$

This is an echo program with no runtime or standard library. It's meant to be compiled with -nostdlib on an amd64 Linux system.

static signed long mywrite(int fd, const void *buf, unsigned long count) {
    signed long retval;
    __asm__ __volatile__(
        "syscall" :
        "=a" (retval) :
        "a" (1), "D" (fd), "S" (buf), "d" (count) :
        "rcx", "r11", "memory"
    );
    return retval;
}

static void myexit(int status) __attribute__((__noreturn__));
static void myexit(int status) {
    __asm__ __volatile__(
        "syscall" :
        :
        "a" (60), "D" (status) :

    );
    __builtin_unreachable();
}

static unsigned long mystrlen(const char *str) {
    const char *pos = str;
    while(*pos) ++pos;
    return pos - str;
}

static void writearg(char *str, char end) {
    unsigned long size = mystrlen(str) + 1;
    unsigned long written = 0;
    str[size - 1] = end;
    do {
        signed long result = mywrite(1, str + written, size - written);
        if(result < 0) myexit(1);
        written += result;
    } while(written < size);
}

void _start(void) __attribute__((__naked__, __noreturn__));
void _start(void) {
    __asm__(
        "pop %rdi\n\t"
        "mov %rsp, %rsi\n\t"
        "jmp mymain"
    );
}

static void mymain(int argc, char *argv[]) __attribute__((__noreturn__, __used__));
static void mymain(int argc, char *argv[]) {
    if(argc <= 1) {
        myexit(mywrite(1, "\n", 1) != 1);
    }
    int i;
    for(i = 1; i < argc - 1; ++i) {
        writearg(argv[i], ' ');
    }
    writearg(argv[i], '\n');
    myexit(0);
}

Some of my concerns:

  • Is my program's behavior fully compliant with the standard for echo?
  • Am I making any unwarranted assumptions that could make my code not work on a future release of Linux (or compiler)? In particular, are popping argc and the way I'm overwriting null terminators in argv values okay?
  • Since my code is Linux-on-amd64-only anyway, are there any other assumptions that I can make? For example, can I assume that Linux will always have continuous argv values, and so just make one big write call after swapping out all the nulls, instead of one per argument? (I know I'd still have to loop write in case of partial writes. I also know I could just copy the strings around myself, but I'd rather write them from where I got them.)
  • Instead of having _start as an assembly stub and my real code in mymain, is there any way I can put my real code in _start but still be able to safely get ahold of the command-line arguments?
\$\endgroup\$
4
  • \$\begingroup\$ Why use unsigned long for pointer differences? Does Linux specify that? I'd expect size_t or ptrdiff_t. \$\endgroup\$
    – chux
    Commented Apr 25, 2020 at 9:58
  • \$\begingroup\$ @chux-ReinstateMonica Because I'd either have to typedef it myself, in which case there's no portability win, or #include a header from the standard library to get it, which defeats the purpose of what I did entirely. \$\endgroup\$ Commented Apr 25, 2020 at 14:15
  • \$\begingroup\$ And I'm pretty sure the AMD64 ABI does specify that (not 100% sure about the signed-ness, but the result I'm getting is always both small and non-negative, so I don't think it matters). \$\endgroup\$ Commented Apr 25, 2020 at 14:17
  • \$\begingroup\$ Please do not edit the question after it has been answered, especially please do not edit the code after the question has been answered codereview.stackexchange.com/help/someone-answers. \$\endgroup\$
    – pacmaninbw
    Commented Apr 27, 2020 at 0:30

1 Answer 1

2
\$\begingroup\$

Is my program's behavior fully compliant with the standard for echo?

Code does not process the string as in the OPERANDS section. In particular:

Code does not support \c: "Suppress the <newline> that otherwise follows the final argument in the output. ..."

Am I making any unwarranted assumptions that could make my code not work on a future release of Linux (or compiler)? In particular, are popping argc and the way I'm overwriting null terminators in argv values okay?

I see no trouble with argc.

Overwriting the null terminators in argv may/may not be OK, but is not needed. I could foresee future restrictions. Alternative: write the argv[i] and then the separator.

Other issues

No comment.

\$\endgroup\$
6
  • \$\begingroup\$ Isn't \c an XSI requirement, not a POSIX requirement? \$\endgroup\$ Commented Apr 25, 2020 at 16:09
  • \$\begingroup\$ @JosephSible-ReinstateMonica "\c is an XSI requirement". Unknown how/if required in POSIX. Yet suppressing the \n is useful. \$\endgroup\$
    – chux
    Commented Apr 25, 2020 at 16:14
  • \$\begingroup\$ Also, isn't the question you linked about modifying argv[x], but I'm modifying argv[x][y]? \$\endgroup\$ Commented Apr 25, 2020 at 16:48
  • 1
    \$\begingroup\$ @JosephSible-ReinstateMonica That post delves into modifying argc, argv, argv[]` and argv[][]. As C says, "strings pointed to by the argv array shall be modifiable by the program," so your approach looks OK for now. Since you asked about "unwarranted assumptions that could make my code not work on a future release of Linux", IMO, that is one of those dark corners of C that may disappear in the future. \$\endgroup\$
    – chux
    Commented Apr 25, 2020 at 17:07
  • 1
    \$\begingroup\$ @JosephSible-ReinstateMonica Yes. So use good engineering practices. Just because a corner of the language allows something, do you really want to go there? For future release concerns, staying toward the middle of the language is better. Your call. \$\endgroup\$
    – chux
    Commented Apr 25, 2020 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.