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 inargv
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 bigwrite
call after swapping out all the nulls, instead of one per argument? (I know I'd still have to loopwrite
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 inmymain
, 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?
1 Answer 1
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 inargv
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.
-
\$\begingroup\$ Isn't
\c
an XSI requirement, not a POSIX requirement? \$\endgroup\$Joseph Sible-Reinstate Monica– Joseph Sible-Reinstate Monica2020年04月25日 16:09:17 +00:00Commented 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– chux2020年04月25日 16:14:38 +00:00Commented Apr 25, 2020 at 16:14
-
\$\begingroup\$ Also, isn't the question you linked about modifying
argv[x]
, but I'm modifyingargv[x][y]
? \$\endgroup\$Joseph Sible-Reinstate Monica– Joseph Sible-Reinstate Monica2020年04月25日 16:48:33 +00:00Commented Apr 25, 2020 at 16:48 -
1\$\begingroup\$ @JosephSible-ReinstateMonica That post delves into modifying
argc, argv,
argv[]` andargv[][]
. As C says, "strings pointed to by theargv
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– chux2020年04月25日 17:07:17 +00:00Commented 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– chux2020年04月25日 17:16:35 +00:00Commented Apr 25, 2020 at 17:16
Explore related questions
See similar questions with these tags.
unsigned long
for pointer differences? Does Linux specify that? I'd expectsize_t
orptrdiff_t
. \$\endgroup\$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\$