1
\$\begingroup\$

I am sorry for the very long description. But i think some information of how /proc/self/cmdline works and how it can be used to get a integer input was necessary. You may skip the sections you don't need.

While part of this task is related to C and Linux, it can be done in any almost language that can start other programs / OS where programs can be started with arguments.

Background

For golfed C programs (and possible others) that takes a number as input, it can be shorter to just use a argv string and interpret the bytes of it as a integer and don't bother with parsing a ASCII string (decimal) representation of the number. In C this can be done by main(a,v)int **v{a=*v[1].... This is shorter than parsing a ASCII string like this main(a,v)int **v{a=atoi(v[1]).... The problem is that it isn't that trivial to create an argument list so that a=*v[1] contains a desired value.

So the idea came to make a new code golf task to create a program that creates such a layout.

Memory layout

The following applies to Linux but the task should also work on a different OS with a similar way to start a program as the exec*()-family on Linux (it doesn't matter if a new process is started or if the current one is replaced).

I also don't care if the hack of int a=*v[1] works on your platform or not.

The arguments and the /proc/$PID/cmdline have the following layout:

  V--- argv[0] points here        V--- argv[1] points here
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| . | / | a | . | o | u | t |\0 | A | R | G | 1 |\0 | A | R | G | 2 |\0 | ...
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
  ^------Program name---------^   ^---Argument 1--^   ^---Argument 2--^
Interpret this 4 bytes as integer:^-----------^   

The command-line arguments appear in this file as a set of strings separated by null bytes ('\0'), with a further null byte after the last string.

Task

Write a program that takes a number as input and call a program in a way that /prog/$PID/cmdline of that program contains the binary representation of that number (or would contain it if it would run on Linux, what matters are is which arguments you give to the called program).

This means, you have to call a program with possible multiple arguments. This arguments have to contain the the binary byte representation of that desired number. For every 0-byte inside this number, you have to generate a new argument, but for every non-0-byte you have to continue using the same argument.

You may or may not use this memory layout for the integer 0x12003400 (assuming little endian):

  V--- pointer 1 points here
       V--- pointer 2 points here
                 V--- pointer 3 points here
+----+----+----+----+----+
|0x00|0x34|0x00|0x12|0x00| <- last 0x00 ends the string of pointer 3
+----+----+----+----+----+

Or this

  V--- pointer 1 points here
              V--- pointer 2 points here
                             V--- pointer 3 points here
+----+     +----+----+     +----+----+
|0x00| ... |0x34|0x00| ... |0x12|0x00| 
+----+     +----+----+     +----+----+

Or any other layout with similar effect.

And may or may not call the program like this execl("./program_name",pointer1,pointer2,pointer3,NULL);

Rules
  • Write a full running program that calls a other programs with some of the arguments created like mentioned before.
  • While i observed this layout on Linux, the task should also work on other OSs with the following rules:
    • You can call a other program with multiple arguments where each argument is '\0'-terminated and doesn't contain any '\0' byte inside the argument.
    • It uses 8 bit per byte.
  • The number is given as a written ASCII string or a user input in a base you choose. If needed a prefix (like 0x) can be required.
  • You must set the arguments before or while calling. It isn't allowed to pause the called program and somehow overwrite argv in it.
  • You can use signed integers, unsigned integers or floating point numbers. They have to use at least 4 bytes and need to have a fixed size.
  • For integer, at least 2^32-1 different values have to be supported, for floats at least 2^31 different values have to be supported.
  • The name of the called program can be hardcoded or a different parameter (it doesn't matter from where you get it). But it has to be a valid program name (not ., /, .. ...) and it is different from the current program name.
  • The number can be placed at any argv-number, including 0 as long as it doesn't change the name of the called program. But it has to be always at the same one.
  • There can be any amount of arguments after the number is represented, as long as it is still possible to call a program.
  • You can choose the endianness.
  • The number format has no padding bits.
Examples

A non-golfed example in C:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main( int argc, char **argv )
{
    if(argc<2)
        { exit(1); }
    int n = atoi( argv[1] ); //the number we should give to the called program
    unsigned char buffer[512]; //contains the binary representation of the number + 0-bytes
    memset( buffer, 0, sizeof buffer ); //Set all to 0 so the last argument ends automatically
    memcpy( buffer, &n, sizeof n ); //add the binary representation to the buffer
    unsigned char *args[4]; //pointers to the start of the "strings" containing the number
    args[0] = buffer; //first argument always starts at the beginning
    size_t position = 0; //position in the buffer
    for( size_t i = 0; i<3; i++ ) //loop for all relevant arguments
    {
        //As long as the current byte is not 0, it is still part of the previous argument
        while( buffer[position]!='\0' )
            { position++; }
        position++; //The ending 0 is still part of the previous argument
        args[i+1] = &buffer[position];
    }
    
    execl( "./a.out", "./a.out", args[0], args[1], args[2], args[3], NULL );
    perror( "execv" );
    return 0;
  }

Example program to be called (not part of the task):

That works when the layout it the same, little endian

#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

int main( int argc, uint32_t **argv )
{
    uint32_t a = *argv[1];
    printf( "%"PRIu32"\n", a );
}

That should work independent of how argv is positioned in memory.

#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

int main( int argc, char **argv )
{
    uint32_t result = 0; //should contain the number passed as binary representation to argv
    unsigned index = 1;  //Which argv the current byte is from. Start with argv[1]
    unsigned positionInString = 0; //Current byte inside the current argv[n]
    //32 bit number with 4 bytes
    for(unsigned byte = 0; byte<4; byte++ )
    {
        if( index>=argc )
        {
            fprintf( stderr, "argv is not big enough\n" ); 
            return 1; 
        }
        //Get a single byte
        uint8_t currentValue = argv[index][positionInString] ;
        //move it to the correct place. Little endian
        result |= ( (uint32_t) currentValue ) << (8*byte);
        positionInString++; 
        if( !currentValue ) //use next argument when current argument ends
        {
            index++;
            positionInString=0;
        }
    }

    printf( "result %"PRIu32"\n", result );
}


\$\endgroup\$
4
  • \$\begingroup\$ Probably should be rewritten to use an OS-agnostic term instead of the Linux-specific "/proc/self/cmdline" \$\endgroup\$ Jul 20, 2023 at 10:48
  • \$\begingroup\$ The first codeblock doesn't even compile for me. \$\endgroup\$ Jul 20, 2023 at 10:58
  • \$\begingroup\$ @SuperStormer Thank you. Sorry, changed a variable name and forgot one occurrence. Fixed it. \$\endgroup\$ Jul 20, 2023 at 12:24
  • \$\begingroup\$ @SuperStormer Which term would that be? \$\endgroup\$ Jul 20, 2023 at 12:25

1 Answer 1

2
\$\begingroup\$

Python, 64 bytes

-2 bytes thanks to corvus_192

import os
os.execl(*"aa",*int(input()).to_bytes(4).split(b'\0'))

Uses big endian and assumes binary is named "a".

\$\endgroup\$
6
  • \$\begingroup\$ It doesn't call a at all on my system ($PWD is added to $PATH). \$\endgroup\$ Jul 20, 2023 at 11:43
  • \$\begingroup\$ It's a function. \$\endgroup\$ Jul 20, 2023 at 11:44
  • 2
    \$\begingroup\$ Write a full running program that call a other programs with some of the arguments created like mentioned before. For this task, a function alone doesn't make sense, since the goal is to start a program. \$\endgroup\$ Jul 20, 2023 at 12:20
  • 4
    \$\begingroup\$ @12431234123412341234123 the default on this site is to allow both functions and programs. If you want to go against that default, you should clearly (i.e. in bold) state that in the question. \$\endgroup\$
    – The Thonnu
    Jul 20, 2023 at 13:09
  • 2
    \$\begingroup\$ I think b'\0' also works and is 2 bytes shorter \$\endgroup\$
    – corvus_192
    Jul 20, 2023 at 16:33

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.