mojo's Blog

hexdump 본문

42 seoul

hexdump

_mojo_ 2022. 2. 25. 14:54

Hexdump

 

redirection 없이 시스템의 hexdump 명령과 동일한 기능을 하는 ft_hexdump 라는 프로그램을 작성해야 한다.

우선 처리해야 하는 옵션은 -C가 유일하다고 명시되어 있을텐데 -C 가 없을 경우도 처리해줘야 한다.

그리고 errno 변수를 사용할 수 있다.

 

 

※ hexdump 케이스 살펴보기

 

① hexdump 

-C 옵션이 없는 hexdump 를 우선 살펴보도록 한다.

tail 명령어와 동일하게 hexdump 뒤에 인자가 없을 경우 타이핑이 가능한 상태가 된다.

 

타이핑 가능한 상태 (사진 첨부하기)

 

이 상태에서 [ctrl + D] 키를 누르면 어떠한 것도 출력이 되지 않은 상태로 종료가 된다.

 

 

당연히 인풋이 없으니 출력이 아무것도 되지 않는다는 것은 굉장히 정상적인 일이다.

그러나 필자는 가장 마지막 주소를 출력하도록 해서 틀린 경험이 있다.

구현할때 이 부분을 한번쯤은 고려해볼 필요가 있다.

 

그렇다면 hexdump 를 인자 없는 상태에서 타이핑을 한다면 다음과 같은 결과가 나온다.

 

 

42 서울을 하신 분들 중에 C02 마지막 문제까지 해결하신 분이라면 굉장히 익숙할 것이다.

즉, 메모리 주소가 0000000 부터 시작하여 (7자리 조심) 가장 마지막 주소인 000003e 가 출력되고 종료되는 것을 알 수 있다.

그리고 타이핑 했던 문자들을 16진수로 변환하여 출력하는 것도 알 수 있다.

좀 더 디테일하게 들어가기 전에 우선 아래의 사진을 보도록 한다.

 

 

우선, 주소와 문자별로 16진수로 변환한 값이 빈칸을 곁들여서 출력이 된 후에 바로 라인개행을 해준다.

그리고 주소 0000030 인 부분을 잘 처리해줘야 하는데 이때는 16 개의 문자가 아닌 1 ~ 15 개의 문자 즉, 나머지 문자들에 대한 처리가 이뤄지는 부분이다. 

그래서 가장 마지막에 0a('\n') 가 들어가면서 바로 라인개행을 해서 000003e 를 출력하도록 하면 안된다.

위 사진과 같이 공백을 출력하도록 해서 0000000, 0000010, 0000020 주소와 동일하게 동일한 지점에서 라인개행이 이뤄지도록 구현해야 하는 것이 조심해야 할 포인트다.

그리고 가장 마지막 주소인 000003e 를 출력함과 동시에 라인개행을 처리하는 것 또한 조심해야 한다.

 

다른 케이스를 살펴보도록 한다.

동일한 문자가 계속해서 들어오게 될 경우를 우선 살펴보도록 한다.

 

 

뜬금없는 "*" 만 꼴랑 있는걸 확인할 수 있다.

즉, 동일한 문자가 계속해서 읽어질 경우 "*" 으로 처리해주는 것을 알 수 있다.

현재까지 읽은 버퍼(cur)와 이전에 읽은 버퍼(before) 16개 문자를 서로 비교해가면서 동일할 경우 "*" 를 처리해주면  되며 단 한번의 "*" 를 처리해야 하므로 이를 처리하기 위한 변수 하나를 추가로 설정해줘야 해줘야 한다.

 

그렇다면 다음 구조체를 보도록 한다.

 

typedef struct s_info
{
	char	cur[16];
	char	before[16];
	int		star_op;
	int		option;
} t_info;

 

  • cur : 현재 읽어드린 버퍼에 대한 정보가 담기도록 하기 위한 char 배열이다.
  • before : 16 개의 문자 단위로 cur 버퍼에 읽은 후에 그 다음 문자를 읽는 과정에서 이전 정보를 저장하기 위한 char 배열이다.
  • star_op : "*" 를 단 한번 처리하기 위한 용도로 설정한 변수이다. 
  • option : -C 옵션이 있는지 없는지를 판단하기 위한 변수이다.

 

② hexdump -C

이번엔 -C 옵션이 추가된 경우를 살펴보도록 한다.

 

 

이번엔 주소 8글자와 각 문자별 16진수, 그리고 문자열 양 옆에 '|' 가 추가로 생긴것을 알 수 있다.

옵션이 없었을 경우와 비교해보면, 주소가 8글자로 늘어났고 문자열을 추가로 출력해주는 것을 알 수 있다.

이때, 출력이 불가능한 문자일 경우 '.' 으로 대체한 것을 알 수 있다. (출력 가능한 문자는 ' ' 부터 '~' 까지이다)

 

 

동일한 문자가 나올 경우도 hexdump 와 동일하게 '*' 으로 대체한 것을 알 수 있다.

 

 

③ hexdump [파일]  or  hexdump [파일1] [파일2] [파일3] ...

이번엔 hexdump 에 옵션이 없을 경우에 인자가 추가된 경우의 케이스를 살펴보도록 한다.

 

 

Makefile 에 대해서 hexdump 를 해보았다.

주소 0000000 부터 시작하여 가장 마지막 주소인 0000110 까지 출력되는 것을 알 수 있으며 Makefile 에 있는 문자들을 16 진수로 변환한 값들이 출력되는 것을 알 수 있다.

 

 

이번엔 유효하지 않은 파일 xxx 에 대해 hexdump 를 해보았다.

이 경우 이전에 구현했던 tail 명령어와 동일하게 에러 문구를 띄우면 된다.

문제에서 errno 변수를 사용하라고 언급되었는데 이 경우에 사용하면 된다는 것을 짐작할 수 있다.

 

 

이번엔 인자가 여러개 주어진 경우에 대해 hexdump 를 해봤다.

중간에 유효하지 않은 파일이 존재할 경우 에러문구를 띄우면서 연속으로 주소와 16진수를 출력하는 것을 알 수 있다.

즉, 파일을 여러개 읽는 과정에서 연속적으로 cur 버퍼에 이어서 문자를 받도록 구현해줘야 하는 것을 짐작할 수 있다.

 

 

이번엔 인자가 여러개 주어질 때 전부 유효하지 않은 경우이다.

전부 유효하지 않을 경우, 에러 문구를 띄우면서 가장 마지막에 있는 파일에 대해서 Bad file descriptor 문구를 띄우는 것을 알 수 있다.

즉, 하나라도 파일이 유효하다면 Bad file descriptor 에러 문구를 띄우지 않은 것을 위에서 확인했으므로 이에 대한 에러 처리도 신경써줘야 한다.

 

 

④ hexdump -C [파일]  or  hexdump -C [파일1] [파일2] [파일3] ...

이번엔 옵션이 추가된 상태로 파일이 인자로 들어올 경우를 살펴보도록 한다.

 

 

-C 옵션을 추가하여 Makefile 에 담긴 텍스트가 16개의 문자씩 출력되는 것을 알 수 있다.

이때 -C 옵션이 없었을 경우와 달리 주소가 8자리라는 것을 잊지 말도록 한다.

 

 

파일이 유효하지 않은 경우 옵션이 없었을 때와 동일하게 에러 문구를 띄우는 것을 알 수 있다.

 

 

이번엔 여러 파일을 인자로 설정했을 때의 hexdump -C 이다.

연속으로 읽어드린 문자를 출력하는 것을 알 수 있고 중간에 유효하지 않은 파일에 대해 에러 문구를 띄우는 것도 알 수 있다.

그리고 가장 마지막에 남아있는 버퍼와 가장 마지막에 도달한 주소를 출력하는 것을 알 수 있다.

그러나 가장 마지막에 남아있는 버퍼가 출력되지 않고 마지막에 도달한 주소만 출력된 이유는 현재 Makefile 이 16 문자 단위로 정확하게 끊기기 때문이다.

 

 

hexdump 와 동일하게 에러 문구가 출력되는 것을 알 수 있다.

 


 

※ hexdump 구현 방법

 

구현하기 전에 이전에 봤던 구조체 정보를 다시 살펴보고 진행하도록 한다.

 

typedef struct s_info
{
	char	cur[16];
	char	before[16];
	int		star_op;
	int		option;
} t_info;

 

  • cur : 현재 읽어드린 버퍼에 대한 정보가 담기도록 하기 위한 char 배열이다.
  • before : 16 개의 문자 단위로 cur 버퍼에 읽은 후에 그 다음 문자를 읽는 과정에서 이전 정보를 저장하기 위한 char 배열이다.
  • star_op : "*" 를 단 한번 처리하기 위한 용도로 설정한 변수이다. 
  • option : -C 옵션이 있는지 없는지를 판단하기 위한 변수이다.

 

① int main(int argc, char **argv)

우선 argc, argv 값에 따라 -C 옵션이 있는지 없는지에 대한 여부를 판단해야 한다.

첫 시작인 main() 함수에서 t_info * 타입인 변수 t 를 선언하여 malloc 을 해준 후 star_op, option 에 대한 초기화 작업을 진행해야 한다.

main() 함수에 대한 코드를 보도록 한다.

 

int	main(int argc, char **argv)
{
	t_info	*t;

	t = (t_info *)malloc(sizeof(t_info));
	t->star_op = 0;
	t->option = 0;
	if (argc >= 2 && is_option(argv[1]))
		t->option = 1;
	if (t->option)
	{
		if (argc == 2)
			go_write(t);
		else if (argc >= 3)
			read_file(argc, argv, t);
	}
	else
	{
		if (argc == 1)
			go_write(t);
		else if (argc >= 2)
			read_file(argc, argv, t);
	}
	free(t);
	return (0);
}

 

option 은 -C 인지 아닌지에 대한 여부를 확인하기 위한 변수로 is_option() 함수를 이용하여 "-C" 이 argv[1] 라면 옵션이 달린 경우이므로 option 값에 1을 할당해준다.

그래서 option 이 존재할 경우와 option 이 존재하지 않는 경우 두 가지로 나눠서 처리가 가능하다.

 

  • option(-C)이 존재할 경우 : 파일이 들어오지 않을 경우는 argc 값이 2 이므로 input 처리를 위한 go_write(), 파일이 들어올 경우 argc 값이 3 이상이므로 argc, argv 를 read_file() 함수에 인자로 넣어주도록 한다.
  • option(-C)이 존재하지 않을 경우 : 파일이 들어오지 않을 경우는 argc 값이 1 이므로 input 처리를 위한 go_write(),  파일이 들어올 경우 argc 값이 2 이상이므로 argc, argv 를 read_file() 함수에 인자로 넣어주도록 한다.

 

그리고 모든 작업이 종료된다면 더이상 t 에대한 메모리 영역이 필요 없으므로 free(t) 를 해준 모습이다.

그렇다면 파일이 인자로 들어오지 않을 경우 즉, go_write() 함수에 대해서 알아보도록 한다.

 

 

② void go_write(t_info *t)

main() 함수에서 미리 세팅해둔 t_info 를 포인터 타입으로 받음으로써 input 을 한 문자씩 읽을 준비를 한다.

우선 go_write() 함수에 대한 코드는 다음과 같다.

 

void	go_write(t_info *t)
{
	int				i;
	unsigned int	length;
	char			c;

	i = 0;
	length = 0;
	while (read(0, &c, 1))
	{
		if (errno)
			return ;
		t->cur[i++] = c;
		length++;
		if (i % 16 == 0)
		{
			operation(t, length);
			i = 0;
		}
	}
	print_remainder(t, length);
}

 

한 문자씩(c) 읽어들여서 더 이상 문자를 읽지 않을때까지 작업을 반복하도록 한다.

t_info 구조체에 char 타입의 배열 cur 을 선언하였는데 현재 읽은 문자에 대해 넣어주기 위한 버퍼 역할이라고 볼 수 있다.

즉, 한 문자씩 읽었을 경우 읽은 문자를 현재 버퍼 cur 에 넣어줌으로써 16 개 단위로 문자를 읽어들인 경우, operation() 함수를 통해 메모리 주소, 각 문자의 16진수 변환값, 그리고 옵션의 여부에 따른 문자 출력 or 출력하지 않을지를 결정하도록 한다.

 

 

③ void operation(t_info *t, unsigned int length)

옵션의 여부에 따라 출력결과가 약간의 차이가 있었음을 위에서 확인했었다.

따라서 옵션에 따라 다른 처리를 하도록 아래와 같이 구현하였다.

 

void	operation(t_info *t, unsigned int length)
{
	if (t->option)
	{
		if (ft_strcmp(t->cur, t->before))
		{
			print_memory_o(t->cur, length - 16, 16);
			t->star_op = 0;
		}
		else if (!(t->star_op++))
			write(1, "*\n", 2);
		ft_strcpy(t->before, t->cur);
	}
	else
	{
		if (ft_strcmp(t->cur, t->before))
		{
			print_memory_x(t->cur, length - 16, 16);
			t->star_op = 0;
		}
		else if (!(t->star_op++))
			write(1, "*\n", 2);
		ft_strcpy(t->before, t->cur);
	}
}

 

ft_strcmp 함수를 통해서 현재 버퍼(cur)와 이전 버퍼(before)에 대해서 동일하지 않을 경우(0 이외의 값) 현재 버퍼에 담긴 문자열과 주소 출력을 위한 length - 16, 그리고 16 개의 문자를 출력하기 위한 16으로 3 가지 인자를 print_memory_o 함수 또는 print_memory_x 함수를 호출하도록 한다.

 

 

length - 16 을 인자로 보내는 이유 ?

 

주소는 항상 0000000 or 00000000 으로 시작하였다. (옵션에 따라 7글자, 8글자로 결정)

그렇다면 0 부터 시작한다는 건데 현재 length 값은 현재까지 버퍼를 읽어들인 총 길이이다.

즉, 0 부터 출력하기 위해서 length - 16 을 인자로 보내도록 해야 한다.

만약 length 를 보내게 된다면 0000000 부터가 아닌 0000010 부터 시작하게 된다.

 

 

else if(!(t->star_op++)) 일 경우 "*\n" 을 출력하는 이유 ?

 

ft_strcmp(t->cur, t->before) 값이 0이라는 것은 즉, 이전 버퍼와 동일한 문자열이 되었다는 것을 암시할 수 있다.

아래와 같은 상황이 된 경우라고 볼 수 있다.

 

 

하나의 asterisk 을 출력하도록 하기 위해서 t->star_op 값이 0일 경우에만 처리하도록 하며 0이 아닐 경우에는 무시해주면 된다.

그리고 다음 버퍼를 읽기 위해서 이전 버퍼를 현재 버퍼에 담긴걸 복사해주도록 한다. (asterisk 에 대한 처리를 하기 위함)

 

 

④ void read_file(int argc, char **argv, t_info *t)

이번엔 파일이 인자로 들어오게 된 경우로 여러개의 파일을 읽고 처리하는 작업과 유효하지 않은 파일에 대해서는 에러 문구 또한 처리해야 하기 때문에 좀 더 까다롭다.

우선 코드는 아래와 같다.

 

void	read_file(int argc, char **argv, t_info *t)
{
	int				i;
	int				fd;
	int				check;
	unsigned int	len;

	i = 1 + t->option;
	check = 0;
	len = 0;
	while (i < argc)
	{
		fd = open(argv[i], O_RDONLY);
		if (fd == -1)
		{
			error_message(argv[0], argv[i++]);
			check++;
			continue ;
		}
		len = go(fd, len, t);
		i++;
		close(fd);
	}
	if (check == (argc - 2 + (t->option ^ 1)))
		bad_file(argv[0], argv[argc - 1]);
	else
		print_remainder(t, len);
}

 

i = 1 + t->option 으로 설정한 이유 ?

 

옵션이 있었을 경우 t->option 은 1, 옵션이 없었을 경우 t->option 은 0 이였다.

argv 값을 index 으로 접근하기 위해서 옵션이 있었을 경우 argv[2], argv[3], ... 순으로 파일 이름을 받아올 수 있으며 옵션이 없었을 경우 argv[1], argv[2], ... 순으로 파일 이름을 받아올 수 있다.

 

check 값은 아래와 같은 경우를 처리하기 위한 용도이다.

 

 

즉, 모든 파일이 발견되지 못할 경우 가장 마지막 파일에 대해서 Bad file descriptor 문구를 띄우는 것을 처리하기 위해서 check 변수를 통해서 발견하지 못할때마다 check 값을 카운팅도록 하였다. 

이때 파일이 발견되지 못한 경우는 fd = open(argv[i], O_RDONLY) 를 수행한 후에 정상적으로 argv[i] 의 이름을 가진 파일을 열지 못해서 fd 값이 -1 이 된 경우이다.

따라서 check 값을 증가시키면서 error_message() 함수를 통해 위와 동일하게 출력되도록 구현하였다. 

 

 

check == (argc - 2 + (t->option ^ 1)) 일 때 Bad file descriptor 문구를 띄우는 이유 ?

 

argc - 2 + (t->option ^ 1) 값과 같아지게 될 경우 모든 파일들이 유효하지 않은 경우이다.

예를 들어서 위와 같은 경우는 argc = 4 이며 option 은 0 이다.

즉, check 값이 3일 경우 Bad file descriptor 을 출력하도록 해야 한다.

그런데 argc - 2 + (t->option^1) 값이 결국 4 - 2 + (0 ^ 1) = 3 이므로 성립한다.

 

또다른 예로 위 경우에서 -C 옵션이 추가될 경우 argc = 5 이며 option 은 1 이다.

즉, check 값이 3일 경우 Bad file descriptor 을 출력하도록 해야 한다.

그런데 argc - 2 + (t->option^1) 값이 결국 5 - 2 + (1^1) = 3 이므로 성립한다.

 

따라서 check 값이 (argc - 2 + t->option^1) 값과 같아질 경우 가장 마지막 파일에 대해서 Bad file descriptor 임을 출력하도록 처리하였다.

 

 

len = go(fd, len, t); 는 어떠한 의도로 호출하였는지 ?

 

예를 들어 파일1, 파일2, 파일3 을 읽는다고 가정해본다. (파일1의 길이 100, 파일2의 길이 200, 파일3의 길이 300)

우선 초기 len 값은 0 이므로, 첫 파일1 을 읽을 때 길이 0부터 각 문자를 읽는 것을 시작한다.

모든 문자를 go() 함수를 통해 읽은 후에 누적된 len 값을 반환하게 되는데 이때 len 값은 100이 된다.

그리고 파일2 를 읽을 때 go() 함수를 통해 읽은 후에 누적된 len 값은 100 + 200 이 된다.

자연스럽게 파일3 을 읽으면 len 값은 100 + 200 + 300 이 되고 while-loop 를 빠져나온다.

즉, 파일을 연속으로 읽으면서 동시에 문자를 읽은 누적된 길이를 len 으로 보면 되고 이 값은 주소 처리와 버퍼 출력을 위한 용도로 보면 된다.

 

go() 함수를 살펴보기 전에 error_message(), bad_file() 함수를 간단하게 보면 다음과 같다.

 

void	error_message(char *exec_name, char *s)
{
	print_message(basename(exec_name));
	print_message(": ");
	print_message(s);
	print_message(": ");
	print_message(strerror(errno));
	print_message("\n");
}

void	bad_file(char *exec_name, char *s)
{
	print_message(basename(exec_name));
	print_message(": ");
	print_message(s);
	print_message(": ");
	print_message("Bad file descriptor\n");
}

 

이전 페이지에서 구현했던 tail과 동일하게 에러처리를 해주면 되고 "Bad file descriptor" 같은 경우는 직접 문자열을 설정하여 출력하도록 구현하였다.

 

 

⑤ unsigned int go(int fd, unsigned int length, t_info *t)

      unsigned int go_other(int fd, unsigned int length, t_info *t)

 

unsigned int	go(int fd, unsigned int length, t_info *t)
{
	unsigned int		i;
	unsigned int		j;
	unsigned int		len;
	char				tmp[16];

	len = 0;
	if (length % 16 != 0)
	{
		i = (length % 16);
		j = 0;
		len = read(fd, tmp, 16 - (length % 16));
		while (j < len)
			t->cur[i++] = tmp[j++];
		if (len != 16 - (length % 16))
			return (length + len);
		operation(t, length + len);
	}
	return (go_other(fd, length + len, t));
}

unsigned int	go_other(int fd, unsigned int length, t_info *t)
{
	int				i;
	unsigned int	len;
	unsigned int	t_len;
	unsigned int	ret;
	char			tmp[16];

	ret = length;
	while (1)
	{
		i = 0;
		len = read(fd, tmp, 16);
		t_len = len;
		ret += len;
		while (len--)
		{
			t->cur[i] = tmp[i];
			i++;
		}
		if (t_len != 16)
			return (ret);
		operation(t, ret);
	}
	return (ret);
}

 

  • fd : file descriptor 값으로 특정 파일을 읽기 위한 값이다.
  • length : 현재까지 얼마만큼 문자를 읽었는지에 대한 누적 값이다.

 

사실 이 함수를 한번에 처리하도록 하고 싶었지만, line 수 제한(최대 25)때문에 go_other() 함수를 따로 만들어서 구현하게 되었다.

go_other() 함수는 위에서 설명하였던 go_write() 함수와 거의 동일한 구조이다.

그리고 go() 함수는 이전 파일에서 16개 단위로 버퍼에 담지 못할 경우에 대한 처리라고 보면 된다. (첫 파일은 항상 length 값이 0이므로 수행되지 않음)

예를 들어서 파일1의 텍스트 길이가 100이여서 0, 16, 32, 48, 64, 80, 96 에 대한 주소 및 각 문자별 16진수 값이 처리되었다고 가정해보자.

그럼 아직 남아있는 문자 4개에 대해서 다음 파일인 파일2를 읽을 때 연속으로 읽으면서 동시에 주소 및 문자별 16진수 값 처리가 되야하기 때문에 자연스럽게 이어지기 위한 처리작업이라고 보면 된다.

 

따라서 length % 16 != 0 일 경우 이전 파일에서 남아있는 문자 (length % 16) 개를 현재 읽기 위한 파일에 자연스럽게 버퍼를 이어주기 위해 어떠한 작업을 처리한다고 보면 된다.

 

 

len != 16 - (length % 16) 에 대한 조건문은 어떠한 의도로 설정하였는지 ?

 

예를 들어서 파일1에서 문자 100개를 읽어서 length 값이 100이 된 후에 파일2를 읽는다고 가정하자.

이때 파일2의 길이가 10 인 경우와 길이가 20 인 경우를 생각해보자.

 

우선 파일2의 길이가 10일 경우, 파일2의 문자를 최대 16 - (length % 16) 길이 만큼 읽을 경우 len 값은 10이 된다. 

그러나 length 값은 100 + 10 = 110 으로 아직 112 에 미치지 못해서 주소 및 문자별 16진수 값을 출력하는 작업을 할 수 없다.

즉, 16 - (length % 16) = 16 - (100 % 16) = 12 만큼 파일2 에서 문자를 읽어들이지 못할 경우 현재 버퍼에 문자를 연속으로 저장만 해두고 현재까지 문자를 읽어들인 누적 값인 (length + len) 을 반환하면 된다.

 

그 다음으로 파일2의 길이가 20일 경우, 파일2의 문자를 최대 16 - (length % 16) 길이 만큼 읽을 경우 len 값은 12가 된다.

이때는 length 값은 100 + 12 = 112 으로 16으로 나눠 떨어지는 값이므로 주소 및 문자별 16진수 값을 출력하는 작업을 수행할 수 있다.

즉, 16 - (length % 16) = 12 만큼 파일2 에서 문자를 읽어들인 경우 읽은 문자들에 대한 주소 및 문자별 16진수 값을 출력하는 작업을 수행하고 누적된 length + len 값을 go_other() 함수에 보내서 더 읽어들이기 위한 문자들에 대한 처리 작업이 진행되면 된다.

 

go_other() 함수는 go() 함수에 비해 간단하다. 

16개의 문자를 한번에 읽어들이면서 읽어들인 경우 작업을 처리하고 그렇지 않은 경우 파일이 종료된 경우이므로 현재까지 문자를 읽은 누적된 길이 ret 을 반환하면 그만이다.

 

 

⑥ void print_memory_o(char *s, unsigned int size, int how)

 

void	print_memory_o(char *s, unsigned int size, int how)
{
	print_address_o(size, 0);
	if (how != 16)
	{
		print_hex_o(s, how);
		print_blank_o(how);
		print_string_o(s, how);
	}
	else
	{
		print_hex_o(s, 16);
		print_string_o(s, 16);
	}
	write(1, "\n", 1);
}

 

  • s : 현재 읽어들인 버퍼로 길이는 항상 1 부터 16 까지이다.
  • size : 현재 주소에 대한 unsigned int 형 값이다.
  • how : 현재 읽어들인 버퍼의 길이 이다.  

 

print_memory_x() 함수는 print_memory_o() 함수와 거의 동일한 구조이기 때문에 생략하고 위 함수에 대한 구조를 살펴보도록 한다.

 

  • print_address_o() : 매개변수로 받은 size 값으로 주소를 16진수 값으로 출력하기 위한 함수이다.
  • print_hex_o() : 각 문자를 16진수 값으로 출력하기 위한 함수이다.
  • print_blank_o() : 버퍼의 길이가 1 ~ 15 일 경우, 라인을 맞춰주기 위해 공백을 출력하기 위한 함수이다.
  • print_string_o() : 버퍼에 담긴 문자를 순수하게 출력하기 위한 함수이다.

 

how 값이 정확하게 16으로 나눠 떨어질 경우 blank 처리 없이 주소, 문자별 16 진수값, 그리고 문자 출력을 하면 되지만 나눠 떨어지지 않을 경우 blank 처리를 함으로써 라인을 맞춰주면서 동일하게 작업을 처리하도록 해야 한다.

print_address_o() 함수부터 시작해서 print_string_o() 함수에 대해 간단하게 살펴보도록 한다.

 

void	print_address_o(unsigned int address, int fin)
{
	int				i;
	unsigned char	c;
	unsigned char	temp[8];

	i = 0;
	while (i < 8)
	{
		if (address % 16 < 10)
			c = 48 + (address % 16);
		else
			c = 97 + (address % 16 - 10);
		address /= 16;
		temp[i++] = c;
	}
	i -= 1;
	while (i >= 0)
		write(1, &temp[i--], 1);
	if (fin)
		write(1, "\n", 1);
	else
		write(1, "  ", 2);
}

 

print_address_o() 함수이다.

temp 배열에 현재 주소에 대한 값을 담아줌으로써 출력해준다.

매개변수로 받은 fin 변수는 가장 마지막에 도달한 주소만 출력할 지에 대한 용도이다.

fin 값이 0일 경우 아직 끝난게 아니므로 공백을 출력하여 그 다음 문자별 16진수 값을 출력하도록 해줘야 하며 0이 아닐 경우 라인개행 후 종료해주면 된다.

 

void	print_hex_o(char *s, int size)
{
	int				i;
	int				c;
	unsigned char	*tmp;
	unsigned char	hex[3];

	i = 0;
	tmp = (unsigned char *)s;
	hex[2] = ' ';
	while (i < size)
	{
		c = (int) *(tmp + i);
		if (c % 16 < 10)
			hex[1] = 48 + (c % 16);
		else
			hex[1] = 97 + (c % 16 - 10);
		c /= 16;
		if (c % 16 < 10)
			hex[0] = 48 + (c % 16);
		else
			hex[0] = 97 + (c % 16 - 10);
		write(1, hex, 3);
		i++;
		if (i % 8 == 0)
			write(1, " ", 1);
	}
}

 

print_hex_o() 함수이다.

각 문자를 hex 배열에 16진수로 변환하여 저장한 후 출력해준다.

그리고 hexdump 명령어를 자세히 보면 공백처리를 조심해줘야 하므로 위와 같이 8 개의 문자를 16진수로 출력할 때마다 공백을 하나 더 출력해주는 디테일을 잊으면 안된다.

 

void	print_blank_o(int idx)
{
	int		i;

	i = idx;
	while (i < 16)
	{
		write(1, "   ", 3);
		i++;
		if (i % 8 == 0)
			write(1, " ", 1);
	}
}

 

print_blank_o() 함수이다.

16 개의 문자에 대한 처리가 아닐 경우, print_hex_o() 함수에서의 16 진수 값 출력을 공백에 대한 출력으로 대체함으로써 특정 조건에서 공백을 하나 더 출력해주는 디테일을 print_hex_o() 함수와 동일하게 잊으면 안된다.

 

void	print_string_o(char *s, int size)
{
	int				i;
	unsigned char	c;
	unsigned char	*tmp;

	i = 0;
	tmp = (unsigned char *)s;
	write(1, "|", 1);
	while (i < size)
	{
		c = *(tmp + i);
		if (c >= ' ' && c <= '~')
			write(1, &c, 1);
		else
			write(1, ".", 1);
		i++;
	}
	write(1, "|", 1);
}

 

print_string_o() 함수이다.

버퍼에 담긴 문자열을 출력하는 함수로써 좌우에 '|' 를 붙여줬던 것을 위에서 확인했다.

따라서 '|' 를 우선 출력한 후에 문자열을 출력한 후, '|' 을 다시 출력해준다. ( |0123456789abcdef| 와 같은 처리 )

그런데 출력할 수 없는 문자에 대해서 '.' 로 대체했던것 또한 위에서 확인했다. (ex : 라인개행)

따라서 출력가능한 문자일 경우 그대로 문자를 출력하며 그렇지 않을 경우 '.' 을 출력해주면 된다.

 

 

⑦ void print_remainder(t_info *t, unsigned int length)

 

void	print_remainder(t_info *t, unsigned int length)
{
	if (t->option)
	{
		if (length % 16 != 0)
			print_memory_o(t->cur, length - (length % 16), length % 16);
		if (length)
			print_address_o(length, 1);
	}
	else
	{
		if (length % 16 != 0)
			print_memory_x(t->cur, length - (length % 16), length % 16);
		if (length)
			print_address_x(length, 1);
	}
}

 

마지막에 남겨진 버퍼에 대한 작업 처리 및 가장 마지막에 도달한 주소만 출력하도록 하는 함수이다.

모든 파일을 읽어들인 후에 남아있는 버퍼의 문자열의 길이가 16 일 경우 가장 마지막에 도달한 주소만을 출력하면 된다.

그리고 16 이 아닐 경우 아직 출력되지 않은 버퍼에 대한 작업 처리 및 가장 마지막에 도달한 주소를 출력하면 된다.

이때, length 값이 0일 경우 버퍼에 문자를 한번이라도 읽지 못한 경우이므로 마지막에 도달한 주소를 출력하면 안된다. (이 부분을 고려하지 못했음)

 

length - (length % 16) 값을 주소값으로 보낸 이유는 예를 들어서 length 값이 100 이라고 가정한다면, 100 - (100 % 16) = 96 즉, 96 에 대한 주소값을 보내기 위함이다.

즉, 16 의 배수로 주소값을 출력해야 하며 가장 마지막에 도달한 주소는 length 를 인자로 그대로 보내기만 하면 된다.

 


 

hexdump 에 대한 구현 플로우는 대략 이렇다.

옵션이 없을 경우에 대한 함수는 위와 동일하므로 작성하지는 않았다.

많은 삽질을 통해 hexdump 에 대한 모든 케이스들을 살펴보았고 구현했으며 3번의 리트끝에 성공하였다.

리트가 3번이나 있었던 이유는 단순하게 구현하라고 주어졌기 때문이다. (예제가 1도 없음)

'42 seoul' 카테고리의 다른 글

tail  (0) 2022.02.22
Split  (0) 2022.02.22
Comments