I'm working on a project that basically does the same thing as strace(1) using ptrace(). Basically we have a controller.c program that takes an executable as an argument and it outputs any system calls made by the executable (for example % controller ls -l)
We are using the execve() to run the executable file, but we are having a little bit of trouble. execve takes the following arguments
execve( const char *filename, char *const argv[], char *const envp[] )
where filename in this instance would be "ls", and argv[] is a list of the arguments for the given file name. So we have something that looks like this (in the C file)
int main(int argc, char *argv[],char *envp[]){
pid_t child;
child = fork;
if(/* CHILD */){
ptrace(PTRACE_TRACEME,0, NULL, NULL);
if(argc == 2) {
execve(argv[1],NULL,envp);
}
else {
execve( argv[1], /* ARGUMENT LIST */, envp);
}
} else /* PARENT */ {
//PARENT CODE
}
}
So if we get an executable, for example controller ls -l, where argv[0] = "controller", argv[1] = "ls", and argv[2] = "-l", how can we pass the correct parameter at "ARGUMENT LIST" (where the arguments in this case is just "-l" but it could be more)?
Basically, how do we initialize an array of type const char * such that the array has the argument values for the executable? Do we even need to worry about having extra values in the array and just past argv for ARGUMENT LIST?
Thanks for your help!
You can simply pass argv + 1 (to skip your program's name). argv + 1 is a pointer to an array starting from the second element of argv, which is the executed program's name. argv + 1 is equivalent to &argv[1], and you can use whatever style you prefer.
As mentioned in the comments, section 5.1.2.2.1 of the standard specified that argv is indeed null-terminated. The section below is retained for completeness.
In case C doesn't guarantee argv is null-terminated (and it's not clear to me whether it does or not), you can do:
char **new_argv = malloc((argc-1) * sizeof(char*));
for (int i = 0; i < argc - 1) new_argv[i] = argv[i+1];
and use new_argv for the argument list.
execve(argv[1], argv+1, envp);
Related
I am trying to implement a simple shell program that runs user input commands. I want the user to enter "ls" or "dir" and have the shell run /bin/ls or /bin/dir . For the execve argument what would be correct:
char *args[] ={"/bin/", "ls", NULL};
//Fork, pid, etc...then
execve(args[0], args+1, NULL);
Or would it be something different?? I've see some people using /bin/ls as the pathname and then no arguments or ls as the pathname and \bin for the environment? I tried what I had above and it didn't work so I want to know if it is the arguments I am sending it or whether I should be looking elsewhere in my code for the problem. Note I am not interested in other variations of execve such as execvp. I am on a Linux system. Thanks
PATHNAME
The pathname in execve() must be the full path to the executable, such as /bin/ls. If using execvpe(), you could use ls alone as the pathname, but as you already specified, you don’t want to use that.
ARGUMENTS
The arguments should be an array of strings, one for each space-separated argument specified on the command line. The last one should be NULL. The first argument should be the pathname itself. For example:
char* args[] = {"/bin/ls", "-la", "foo/bar", NULL};
ENVIRONMENT
The environment variables cannot be omitted when using execve(). In some implementations, NULL can be passed as the last argument to execve(), but this is not standard. Instead, you should pass a pointer to a null pointer; essentially an empty array of environment variables.
Putting it together
char *args[] ={"/bin/ls", "-foo", "bar", NULL};
//Fork, pid, etc...then
char* nullstr = NULL;
execve(args[0], args, &nullstr);
From execve [emphasis added]:
int execve(const char *pathname, char *const argv[],
char *const envp[]);
execve() executes the program referred to by pathname. This
causes the program that is currently being run by the calling
process to be replaced with a new program, with newly initialized
stack, heap, and (initialized and uninitialized) data segments.
pathname must be either a binary executable, or a script starting
with a line of the form:
#!interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
argv is an array of pointers to strings passed to the new program
as its command-line arguments. By convention, the first of these
strings (i.e., argv[0]) should contain the filename associated
with the file being executed. The argv array must be terminated
by a NULL pointer. (Thus, in the new program, argv[argc] will be
NULL.)
In your case, the pathname should be "/bin/ls" and not "/bin/". If you want to pass any command line argument with the command, you can provide first argument them with argv vector index 1, second argument with index 2 and so on and terminate the argument vector with NULL.
A sample program which replace the current executable image with /bin/ls program and runs /bin/ls testfile:
#include <stdio.h>
#include <unistd.h>
int main (void) {
char *args[] = {"/bin/ls", "testfile", NULL};
// here you can call fork and then call execve in child process
execve(args[0], args, NULL);
return 0;
}
Output:
# ./a.out
testfile
So the following snippet should either make the prompt in a simple shell program 'my257sh'>, or ''> if -p is used as a command line arg when launching, followed by a string to be used as the custom prompt.
The custom prompt works fine, but I get a seg fault when launching with no additional args.
Can someone tell me what incredibly simple and stupid thing I'm doing wrong?
int main(int argc, char *argv[]) {
char cmdline[MAXLINE]; /* Command line */
char def_prompt[20] = "my257sh";
const char prompt_check[2] = "-p";
char new_prompt[20];
if (argc > 0){
if (strstr(argv[1], prompt_check)) {
strcpy(new_prompt, argv[2]);
}
}
else {
strcpy(new_prompt, def_prompt);
}
signal(SIGINT, sigIntHandler); // ignores ctrl + C interrupt
while (1) {
/* Read */
printf("%s> ",new_prompt);
...
argc is always > 0 : argv[0] contains the name of the program.
Check
If argc > 1
Also in case of -p check that
If argc > 2
You also need to check the length of prompt to avoid copying in too small string
argc contains the number of arguments passed on the shell, including the command itself, so argc will always be > 0 as it is at least 1 if no parameters were given.
If you don't pass any arguments, then argv[1] will contain a NULLpointer. If you pass only one parameter then argv[2] is a NULL pointer.
if (argc > 2)
{
if (strstr(argv[1], prompt_check))
strcpy(new_prompt, argv[2]);
}
else
{
strcpy(new_prompt, def_prompt);
}
Ive been given this code in class:
int main(int argc, char **argv)
{
if(argc)
{
return 1;
}
puts(argv[3]);
return 0;
}
Now, Im supposed to write a second program which executes this and makes it print "Hello World!". So I have to find a way to pass in arguments (I assume with execv) while having argc stay at 0.
This is what I have so far:
int main()
{
pid_t pid = fork();
if(pid == 0)
{
char *argv[] = { "filepath", "placeholder",
"placeholder", "Hello World!" };
execv("filepath", argv);
exit(0);
}
else
{
waitpid(pid, 0, 0);
}
return 0;
}
This is going to run on Linux.
I tried mbj's original suggestion to pass a 0 in the arguments array, which sadly didnt work. However if I put 0 as the first argument, argc becomes 0, but then I get the output "LC_MEASUREMENT=de_DE.UTF-8" when trying to print the arguments. Googling this didnt really help me either.
Im at a total loss here, so any help is appreciated.
Okay, I figured this out now, after some experimenting of my own.
Since passing an array that starts with NULL (0) to execv results in the program printing out LC_MEASUREMENT=de_DE.UTF-8, I realized that it must means that argv[3] is referring to an element in the process environment (LC_MEASUREMENT is one of the environment variables used to configure locale settings in Linux).
Solution with execv
Since execv will copy the current environment to the new program, we just have to modify the environment and put the string "Hello World!" in the correct spot before calling execv. It turns out that the string which gets printed is the one pointed to by index 2 of the environment.
To access the current environment, we need to declare the environ variable outside of main:
external char **environ;
int main()
{
...
And then do this before calling execv:
char *argv[] = { NULL };
environ[2] = "Hello world!";
execv("filepath", argv);
Simpler solution with execve
Instead of declaring external char **environ and modifying the current environment before calling execv, we can use the execve function, which lets us pass in a new array of strings to use as the environment for the program:
char *argv[] = { NULL };
char *envp[] = { "foo", "bar", "Hello World!", NULL };
execve("filepath", argv, envp);
As the above code snippet shows, "Hello World!" needs to be at index 2 of the environment, regardless of which method we use.
How it works
The reason this works is that there's a standard for how command-line arguments and the environment are laid out in process memory when a program is executed: The environment array of char pointers is placed right after the command-line arguments array.
Since these arrays are terminated with a NULL entry, it will look like this:
+---------+---------------------------------------+
| Args | Environment |
+---------+---------+---------+---------+---------+
| NULL | envp[0] | envp[1] | envp[2] | NULL |
+---------+---------+---------+---------+---------+
^ ^ ^
| | |
argv[0] argv[1] ... argv[3]
I hope the ASCII art helps you understand why argv[3] means the same thing as environ[2] when we execute the program like this.
I am writing a program using execl to execute my exe file which is testing and it's work very well and display the output in the Linux CLI. But I have not idea how to change the execl to execv, although I know both of the system call will give the same value. I am confused with the array argument for execv system call
This is my execl sample program
int main(void)
{
int childpid;
if((childpid = fork()) == -1 )
{
perror("can't fork");
exit(1);
}
else if(childpid == 0)
{
execl("./testing","","",(char *)0);
exit(0);
}
else
{
printf("finish");
exit(0);
}
}
can I know how to change the execl to execv. What I read from online, we must set the file path for my exe file and the argument of array . What type of argument need to set for the array in order to ask the program to execute the testing exe file ?
https://support.sas.com/documentation/onlinedoc/sasc/doc/lr2/execv.htmIs it the link consist of the thing I want ? But what I read from it ,the command is request the list the file,not execute the file. Correct me I make any mistake
In order to see the difference, here is a line of code executing a ls -l -R -a
with execl(3):
execl("/bin/ls", "ls", "-l", "-R", "-a", NULL);
with execv(3):
char* arr[] = {"ls", "-l", "-R", "-a", NULL};
execv("/bin/ls", arr);
The char(*)[] sent to execv will be passed to /bin/ls as argv (in int main(int argc, char **argv))
According to the man page the use of execv is quite simple. The first argument is the path as a string to the program you want to execute. The second is an array of string that will be used as the arguments of the program you want to execute. It is the kind of array you get if you get the argv array in your main function.
So the array you will pass as a parameter will be the array received in the main function of the program you execute with execv.
By convention, the first argument should be the program name (the one you try to execute) but it is not mandatory (but strongly recommended since it is the behaviour a lot of programs are expecting). Each other string in the array should be an individual argument.
And of course, the array should be terminated with a NULL pointer to mark the end.
Array example: ["prog_name", "arg1", "arg2", "arg3", NULL]
[] is your array, each string separated with a coma is a frame of your array and at the end you have the null frame.
I hope I am clear enough!
I am writing a simple shell in C. It is actually coming along quite well with i/o redirection and such. One thing that I'd like to add is a way to switch between versions of the exec* functions. Right now I'm sticking to execlp(), execvp(), and execve().
I have all the arguments I want to pass in an array called argv. It is a null terminated array of null terminated strings, so it works fine with execv*, but I can't think of how to get it to work with execlp().
This is what I have now:
if (strcmp(exec_opt, "vp") == 0)
error = execvp(argv[0], argv); /* Execute vp */
else if(strcmp(exec_opt, "lp") == 0)
error = execlp(argv[0], "", argv); /* Execute lp */
else if(strcmp(exec_opt, "ve") == 0)
error = execve(argv[0], argv, environ); /* Execute ve */
else
{
// throw errors about exec_opt
}
if(error != 0)
{
// do something about it
}
In this configuration the compiler doesn't baff at the syntax, but it also doesn't work.
I've also tried
error = execlp(argv[0], (char*) argv); /* As a single string */
char* argv1 = argv[1]; /* don't pass command itself */
error = execlp(argv[0], argv1);
Which do various odd but ultimately incorrect things. Is there a way for me to turn my array into a variable argument list? passing it directly (which makes the most type-sense, since variable argument lists are char* argv[]) yields a compiler error about casting incompatible pointers.
You can't really use execlp() with the array. To use execlp(), you have to write out:
execlp(array[0], array[0], (char *)0);
execlp(array[0], array[0], array[1], (char *)0);
execlp(array[0], array[0], array[1], array[2], (char *)0);
execlp(array[0], array[0], array[1], array[2], array[3], (char *)0);
...
for each alternative number of arguments. This is why execvp() was added to the repertoire (it wasn't part of 7th Edition UNIX™ in 1978, but was part of SUS v2 in 1997). Now you just need execvpe(), which does not exist AFAIK (and I don't know why it isn't provided, either).
7th Edition UNIX did have excevp()
Dave said:
The 7th Edition manual does list execvp.
And ... it does, partially. I think we have an erratum to report since the manual actually contains:
NAME
execl, execv, execle, execve, execlp, execvp, exec, exece, environ – execute a file
SYNOPSIS
execl(name, arg0, arg1, ..., argn, 0)
char *name, *arg0, *arg1, ..., *argn;
execv(name, argv)
char *name, *argv[ ];
execle(name, arg0, arg1, ..., argn, 0, envp)
char *name, *arg0, *arg1, ..., *argn, *envp[ ];
execve(name, argv, envp);
char *name, *argv[ ], *envp[ ];
extern char **environ;
DESCRIPTION
So, execvp() is listed in the NAME section, but there is no synopsis for execvp() (which is what I looked at to come to the conclusion it was missing). There is a reference to execvp() on the next page:
Execlp and execvp are called with the same arguments as execl and execv, but duplicate the shell’s actions in searching for an executable file in a list of directories. The directory list is obtained from the environment.
So, I excuse myself because I scanned the SYNOPSIS and execvp() was omitted from the synopsis. But actually, the system call was present in 7th Edition Unix. I don't think anyone is about to re-release the manuals with the omission fixed.
My print copy (yes, I do have printed properly bound versions of both Volume 1 (ISBN 0-03-061742-1) and Volume 2 (ISBN 0-03-061743-X) of the manual from way back then; I obtained them circa 1989) has the same omission in the SYNOPSIS section.
Try using avcall. I haven't used it myself, I just found an interesting mention of it here: Passing parameters dynamically to variadic functions