'Network' 카테고리의 다른 글

TCP Socket Programming in Python  (0) 2024.01.13
UDP Socket Programming in Python  (0) 2024.01.13

Client

from socket import *

serverName = ""
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_STREAM)

# Client와 Server사이에 TCP연결을 설정한다.
clientSocket.connect((serverName, serverPort))

sentence = input("Input lowercase sentence:")

# 패킷에 목적지 주소를 할당하지 않고,
# 단순히 sentence 문자열 바이트를 TCP연결에게 제공한다.
clientSocket.send(sentence.encode())

# 서버로부터 문자열이 순서대로 쌓인다.
modifiedSentence = clientSocket.recv(1024)

print("From Server: ", modifiedSentence.decode())
clientSocket.close()

 

Server

from socket import *

serverPort = 80
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(("0", serverPort))

# TCP 연결 기다리고 받기 (파라미터 : 큐잉되는 연결의 최대 수)
serverSocket.listen(1)

print("The server is ready to receive")

while True:
    # connectionSocket 소켓을 생성한다.
    connectionSocket, addr = serverSocket.accept()
    sentence = connectionSocket.recv(1024).decode()
    capitalizedSentence = sentence.upper()
    connectionSocket.send(capitalizedSentence.encode())
    connectionSocket.close()

'Network' 카테고리의 다른 글

TCP 연결 과정  (0) 2024.01.15
UDP Socket Programming in Python  (0) 2024.01.13

 

Client 

# 소켓 모듈을 불러온다.
from socket import *

# 목적지 주소(IP, Port)를 설정한다.
# serverName을 이용해서 IP를 얻기위해 DNS 검색이 자동으로 수행된다.
serverName = ""
serverPort = 11111

# clientSocket 클라이언트의 소켓을 생성한다.
# AF_INET : 주소군(family), 하부 네트워크가 IPv4를 사용하고 있음을 나타낸다.
# SOCK_DGRAM : UDP 소켓임을 의미한다.
clientSocket = socket(AF_INET, SOCK_DGRAM)

# 사용자가 message를 입력한다.
message = input('Input lowercase sentence:')

# 목적지 주소 (serverName, serverPort)를 패킷(message.encode())에 붙여서 clientSocket으로 보낸다.
# 출발지 주소도 패킷 붙지만, 이는 코드를 통해 명시적으로 붙지 않고 하부 운영체제가 자동으로 수행된다.
clientSocket.sendto(message.encode(), (serverName, serverPort))

# 서버로부터의 수신을 기다린다.
# 인터넷으로부터 패킷이 소켓에 도착하면 아래 변수들에 할당된다.
# modifiedMessage : 패킷 데이터가 할당된다.
# serverAddress : 패킷의 출발지 주소(서버 주소)가 할당된다. 이미 알고있는 정보기때문에 필요하지는 않지만, 파이썬에서 제공해준다.
# 2048 : 해당 크기의 버퍼를 받아들인다.
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)

# 도착한 메시지가 출려된다.
print(modifiedMessage.decode())

# 소켓을 닫는다.
clientSocket.close()

# 프로세스가 종료된다,

 

Server

from socket import *

serverPort = 11111
serverSocket = socket(AF_INET, SOCK_DGRAM)

# 소켓에 주소를 할당한다.
serverSocket.bind(("", serverPort))

print("The server is ready to receive")

while True:
    message, clientAddress = serverSocket.recvfrom(2048)
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(), clientAddress)

 

'Network' 카테고리의 다른 글

TCP 연결 과정  (0) 2024.01.15
TCP Socket Programming in Python  (0) 2024.01.13

SEQ 방식의 명령 수행

기존의 SEQ는 한 클럭 주기당 하나의 명령어만을 수행합니다.

 

(a)의 하드웨어에서 하나의 명령어를 수행한다면

Combinational logic에서 300ps(s/조)가 소요되고

clock에 의해 register에 값을 쓰는데에 20ps가 소요되어 결과적으로 320ps가 소요됩니다.

 

(a)의 하드웨어로 I1, I2, I3를 수행한다면

I1 I2 I3, 3개의 명령어를 수행하므로 320 * 3 = 960ps가 소요됩니다.

 

SEQ 성능 측정

성능측정을 위한 두 가지 개념이 있습니다.

 

Latency of SEQ

하나의 명령어가 수행되는 시간으로 Latency(지연시간)을 표현합니다.

즉, SEQ의 Latency는 320ps입니다.

 

Throughput of SEQ

1초당 수행되는 명령어의 개수로 Throughtput(처리량)을 표현합니다.

위의 SEQ는 320ps당 1개의 Instruction이 수행되므로, 1s당 31억 2천만개의 Instruction이 처리됩니다.

너무 길기때문에, 십억개의 Instructions 묶음으로 나타내기로 약속한 GIPS를 통해 처리량을 표현하면, 3.12 GIPS가 됩니다.

 

즉, 위 SEQ의 Throghput은 3.12 GIPS 입니다.

 

 

왜 Pipelining?

SEQ의 작업 순서가 변경되지 않으면서, 여러개의 명령어를 동시에 수행할 수 있다면 더 빠른속도를 산출할 수 있을겁니다.

하나의 Instruction은 여러개의 Stage(Fetch, Decode, Execute, Memory, Writeback, PC update)로 나눌 수 있는데,

Instruction을 적절한 개수로 나눠 여러개의 명령어의 완료순서를 바꾸지 않고 동시에 수행할 수 있습니다.

 

위 처럼 하드웨어를 구성했다고 합시다.

A에서 계산하고, 클럭이 rise되어 첫번째 레지스터에 작성되고

B에서 계산하고, 클럭이 rise되어 두번째 레지스터에 작성되고

C에서 계산하고, 클럭이 rise되어 세번째 레지스터에 작성되고

를 반복합니다.

Pipeline으로 각 구간이 위와같이 수행된다고 합시다.

예를들어, A에서 Fetch를 완료한 후 Decode단계에서 B가 Fetch를 진행하는 방식으로 동시에 여러개의 명령어를 수행하는겁니다.

각 구간마다 넘버링하여 하드웨어 상태를 살펴보면 다음과 같습니다.

 

 

Pipelining 성능 측정

SEQ의 처리량, 지연시간과 측정 방식이 완전히 동일합니다.

다만 처리량을 구하는 방법에서 약간 생각할 부분이 있습니다.

 

Latency of Pipelining

하나의 명령어의 수행이 완료되려면 stage A, B, C를 거쳐야합니다.

한 clock의 cycle당 하나의 stage를 완료하므로, clock의 cycle인 120ps에 3을 곱하면 360ps입니다.

즉, 위 Pipelining의 Latency는 360ps 입니다.

 

Throughput of Pipelining

위는 단지 3개의 명령어만 수행하지만, 처리량은 1초동안 명령어가 반복적으로 수행한다고 가정해서 구하므로,

명령어 I1 I2 I3 I1 I2 I3 I1... 를 반복적으로 수행해야한다고 가정하겠습니다.

1초에 수행되는 명령어의 개수를 구해야하는데

1초에 굉장히 많은 명령어가 수행되므로 양 Side 부분은 무시하고 값을 구해도 실제로 수행되는 명령어 수와 크게 다르지 않을겁니다.

 

즉 처리량 계산을 위해 side 부분을 제거해서 0ps부터 600ps 까지의 부분만 보았을 때 위와 같은 형태로 명령어들이 수행됩니다.

이제 처리량 계산을 위해 다음을 생각해봅시다.

 

N개의 부분으로 나뉘어진 하드웨어는 N개의 stage를 동시에 수행할겁니다. (예시에서 N = 3)

또한, 한개의 명령어를 수행하려면 stage A, B, C를 거쳐야하고 한 주기동안에도 stage A, B, C가 모두 수행되고 있습니다.

Clock의 한 주기동안 stage A, B, C가 모두 수행되므로 한개의 Instruction이 수행된다고 생각하면,

1초를 한 주기단위로 나눈 개수(위에서는 1s / 120ps)는 1초동안 수행되는 Instruction 수와 동일합니다.

실제로는 한 주기 동안 I1의 stage 한개, I2의 stage 한개, I3의 stage 한개가 수행되지만

결과적으로 1초에 수행되는 명령어수와 거의 동일하다는 얘기입니다.

 

위의 사실들을 바탕으로 계산해보면,

한개의 Instruction이 120ps 동안 실행된다고 생각했을 때

1 Instruction : 120ps = 8333333333.33 Instruction : 1s 의 비율을 가집니다.

1초에 약 83.333억개를 수행하므로, 이를 10억단위로 묶는 GIPS로 나타내면 약 8.33333 GIPS가 됩니다.

 

즉, 위 Pipelining의 Throughput은 약 8.33 GIPS입니다.

 

 

SEQ vs Pipelining 성능비교

Latency : Pipelining의 지연시간이 SEQ의 지연시간보다 약 1.12(360 / 320)배 더 깁니다.

Throughput : Pipelining의 처리량이 SEQ의 지연시간보다 약 2.67(8.33 / 3.12)배 더 깁니다.

 

Pipelining이 SEQ에 비해 1.12배라는 약간의 명령어 처리시간이 늘어났지만 2.67배라는 처리량을 얻었으므로

더 좋은 성능을 얻었다고 정리할 수 있습니다.

'CS:APP' 카테고리의 다른 글

[CS:APP 2.2] Binary Number Encoding  (0) 2023.10.26
[CS:APP] BombLab Phase6 해설  (0) 2023.10.20
[CS:APP] BombLab Phase5 해설  (0) 2023.10.20
[CS:APP] BombLab Phase4 해설  (0) 2023.10.20
[CS:APP] BombLab Phase3 해설  (0) 2023.10.20

ForEach

요구에 따라 Identified Data의 기본 Colletion로부터 뷰를 계산하는 Structure

struct ForEach<Data, ID, Content> where Data : [RandomAccessCollection](https://developer.apple.com/documentation/Swift/RandomAccessCollection), ID : [Hashable](https://developer.apple.com/documentation/Swift/Hashable)

  • Data는 RandomAccessCollection을 준수해야한다.
  • ID는 Hashable을 준수해야한다.

 

ForEach에서 id를 쓰는 이유

init(
    _ data: Data,
    @ViewBuilder content: @escaping (Data.Element) -> Content
)

 

기본 Initailizer에서 data는 Identifiable 프로토콜을 준수해야한다. (각 View들을 식별하기 위해)

하지만, [Int]와 같은 Identifiable을 준수하지 않는 데이터 유형은 data에 넣을 수 없다.

그래서 넣어보면 컴파일 에러가 생긴다. (Cannot convert value of type '[Int]' to expected argument type 'Range’)

이를 해결하기 위해 id가 존재하는 initalizer를 사용한다.

 

init(
    _ data: Data,
    id: KeyPath<Data.Element, ID>,
    @ViewBuilder content: @escaping (Data.Element) -> Content
)

 

해당 initializer에서는 data가 꼭 Identifieable을 준수할 필요가 없다.

id가 그 역할을 대신하기 때문이다.

ForEach View를 사용할 때 ID로 \.self를 넣어주고는 한다.

 

struct ContentView: View {
    let colors: [Color] = [.red, .green, .blue]

    var body: some View {
        VStack {
            ForEach(colors, id: \.self) { color in
                Text(color.description.capitalized)
                    .padding()
                    .background(color)
            }
        }
    }
}

 

ForEach로 생성되는 각 뷰들이 생성되거나 삭제될 때 그 부분만 업데이트하기위해 ID값을 통해서 뷰를 구별한다.

 

public init(_ data: Data,
                        id: KeyPath<Data.Element, ID>, 
                        @ViewBuilder content: @escaping (Data.Element) -> Content)

 

ForEach 뷰에 전달하는 id인자는 keyPath로 전달하게 되는데, 참조한 keyPath를 통해

data에서 가져온 값을 unique identifier 로 결정지어 각 뷰가 구별되도록한다.

때문에 위 예시에서 \Color.self 를 생략한 \.self를 전달했으므로 각 뷰의 unique identifier는 차례대로.red, .green, .blue가 된다.

그렇다면, data의 각 요소가 같다면 어떻게 해야할까?

 

예를들어 colors 배열이 다음과 같은 경우를 말한다.

let colors: [Color] = [.red, .red, .blue]

한 스택 오버플로우 답변 에서는 struct 구조체로 감싸서 Identifiable 프로토콜을 준수하도록 함을 권장한다.

 

How to allow ForEach layout item to show duplicate item from array in SwiftUI?

I am working with on multiple choose answer quiz app. var itemsTemp = ["ant","bat", "bear", "bee", "bird", "butterfly", "camel", &

stackoverflow.com

 

다음과 같은 구조체를 생성해서 UUID() 함수를 통해 unique identifier을 생성한다.

 

struct MyColor: Identifiable {
    var id = UUID()
    var color: Color
}

 

다음과 같이 keyPath를 .id로 전달해서 각 view가 unique해지도록 한다.

 

struct ContentView: View {
    let colors: [MyColor] = [.init(color: .red), .init(color: .red), .init(color: .blue)]

    var body: some View {
        VStack {
            ForEach(colors, id: \.id) { myColor in
                Text(myColor.color.description.capitalized)
                    .padding()
                    .background(myColor.color)
            }
        }
    }
}

 

Unsigned

 

부호가 없는 양수인 2진수를 10진수로 바꾸는 공식은 다음과 같습니다.

충분히 이해할만합니다.


 

Signed

 

부호가 존재하는 2진수에 대해서는 다음 공식을 적용해야합니다.

 

 


 

적당한 증명

 

왜 이런공식이 나오는지 생각해보자면

아래 w가 3인 binary수를 unsigned와 signed를 매칭시켰습니다.

 

Unsigned

 

000 에서 100이 될 때 앞자리에 1이 생기면서 4가 더해집니다.

001 에서 101이 될 때 앞자리에 1이 생기면서 4가 더해집니다.

 

즉, 어찌보면 당연하지만 앞자리는 4를 단지 더해주는 역할을 합니다.

 

 

Signed

 

000 에서 100이 될 때 앞자리에 1이 생기면서 -4가 더해집니다.
001 에서 101이 될 때 앞자리에 1이 생기면서 -4가 더해집니다.

 

즉, 앞자리는 4를 빼주는 역할을 합니다.

때문에 위의 공식에서 맨 앞자리에 마이너스를 곱해서 더합니다.

 


 

Unsigned와 Signed의 관계성

 

위 공식을 이용해서 B2U에서 B2T를 빼보면, 저 수식이 생길겁니다.

만약, 맨 앞자리 비트가 1이라고 가정하면

다시말해서, Unsigned에서는 양수이고 Signed에서는 음수인 숫자로 한정했을 때,

B2U - B2T = 2^w가 됩니다.

 

이 관계성은 Signed 타입의 숫자에서 Unsigned 타입의 숫자로 변환하거나 그 반대로 변환할 때 유용합니다.

'CS:APP' 카테고리의 다른 글

Pipelining의 성능 측정과 비교  (0) 2023.12.04
[CS:APP] BombLab Phase6 해설  (0) 2023.10.20
[CS:APP] BombLab Phase5 해설  (0) 2023.10.20
[CS:APP] BombLab Phase4 해설  (0) 2023.10.20
[CS:APP] BombLab Phase3 해설  (0) 2023.10.20

<__isoc_sscanf, read_six_numbers 분석 결론>

 

Phase_2에서 read_six_numbers에 대해 분석해 얻은 결론을 떠올리면 다음과 같습니다.

 

  1. %rsi에서 전달한 문자열을 통해 __isoc99_sscanf에서 어떤 형식으로 입력을 받을지 결정할 수 있습니다. 지금 경우에는 "%d %d %d %d %d %d" 가 전달되었으므로 6개의 정수를 읽는다고 판단할 수 있습니다.
  2. 입력한 문자열이 __isoc99_sscanf 를 진행하면서 %rdi에서 전달되어 sscanf 함수를 통해 정수 배열형태로 %rsp에 저장됩니다.
  3. read_six_numbers에서 정수 배열 개수가 5개 이하라면 폭탄을 터뜨립니다.
  4. read_six_numbers는 반환값으로 입력한 정수의 개수를 반환한다. 더 자세히 말하면 read_six_numbers는가 %rax에 저장하지 않으므로,  __isoc99_sscanf 함수가 %rax에 입력한 정수의 개수를 반환합니다.

Phase_6

Dump of assembler code for function phase_6:
   0x0000555555401510 <+0>:	push   %r14
   0x0000555555401512 <+2>:	push   %r13
   0x0000555555401514 <+4>:	push   %r12
   0x0000555555401516 <+6>:	push   %rbp
   0x0000555555401517 <+7>:	push   %rbx
   0x0000555555401518 <+8>:	sub    $0x60,%rsp
   0x000055555540151c <+12>:	mov    %fs:0x28,%rax
   0x0000555555401525 <+21>:	mov    %rax,0x58(%rsp)
   0x000055555540152a <+26>:	xor    %eax,%eax
   0x000055555540152c <+28>:	mov    %rsp,%r13
   0x000055555540152f <+31>:	mov    %r13,%rsi
   0x0000555555401532 <+34>:	callq  0x555555401acc <read_six_numbers>
   0x0000555555401537 <+39>:	mov    %r13,%r12
   0x000055555540153a <+42>:	mov    $0x0,%r14d
   0x0000555555401540 <+48>:	jmp    0x555555401567 <phase_6+87>
   0x0000555555401542 <+50>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401547 <+55>:	jmp    0x555555401576 <phase_6+102>
   0x0000555555401549 <+57>:	add    $0x1,%ebx
   0x000055555540154c <+60>:	cmp    $0x5,%ebx
   0x000055555540154f <+63>:	jg     0x555555401563 <phase_6+83>
   0x0000555555401551 <+65>:	movslq %ebx,%rax
   0x0000555555401554 <+68>:	mov    (%rsp,%rax,4),%eax
   0x0000555555401557 <+71>:	cmp    %eax,0x0(%rbp)
   0x000055555540155a <+74>:	jne    0x555555401549 <phase_6+57>
   0x000055555540155c <+76>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401561 <+81>:	jmp    0x555555401549 <phase_6+57>
   0x0000555555401563 <+83>:	add    $0x4,%r13
   0x0000555555401567 <+87>:	mov    %r13,%rbp
   0x000055555540156a <+90>:	mov    0x0(%r13),%eax
   0x000055555540156e <+94>:	sub    $0x1,%eax
   0x0000555555401571 <+97>:	cmp    $0x5,%eax
   0x0000555555401574 <+100>:	ja     0x555555401542 <phase_6+50>
   0x0000555555401576 <+102>:	add    $0x1,%r14d
   0x000055555540157a <+106>:	cmp    $0x6,%r14d
   0x000055555540157e <+110>:	je     0x555555401585 <phase_6+117>
   0x0000555555401580 <+112>:	mov    %r14d,%ebx
   0x0000555555401583 <+115>:	jmp    0x555555401551 <phase_6+65>
   0x0000555555401585 <+117>:	lea    0x18(%r12),%rcx
   0x000055555540158a <+122>:	mov    $0x7,%edx
   0x000055555540158f <+127>:	mov    %edx,%eax
   0x0000555555401591 <+129>:	sub    (%r12),%eax
   0x0000555555401595 <+133>:	mov    %eax,(%r12)
   0x0000555555401599 <+137>:	add    $0x4,%r12
   0x000055555540159d <+141>:	cmp    %r12,%rcx
   0x00005555554015a0 <+144>:	jne    0x55555540158f <phase_6+127>
   0x00005555554015a2 <+146>:	mov    $0x0,%esi
   0x00005555554015a7 <+151>:	jmp    0x5555554015c3 <phase_6+179>
   0x00005555554015a9 <+153>:	mov    0x8(%rdx),%rdx
   0x00005555554015ad <+157>:	add    $0x1,%eax
   0x00005555554015b0 <+160>:	cmp    %ecx,%eax
   0x00005555554015b2 <+162>:	jne    0x5555554015a9 <phase_6+153>
   0x00005555554015b4 <+164>:	mov    %rdx,0x20(%rsp,%rsi,8)
   0x00005555554015b9 <+169>:	add    $0x1,%rsi
   0x00005555554015bd <+173>:	cmp    $0x6,%rsi
   0x00005555554015c1 <+177>:	je     0x5555554015d9 <phase_6+201>
   0x00005555554015c3 <+179>:	mov    (%rsp,%rsi,4),%ecx
   0x00005555554015c6 <+182>:	mov    $0x1,%eax
   0x00005555554015cb <+187>:	lea    0x202c5e(%rip),%rdx        # 0x555555604230 <node1>
   0x00005555554015d2 <+194>:	cmp    $0x1,%ecx
   0x00005555554015d5 <+197>:	jg     0x5555554015a9 <phase_6+153>
   0x00005555554015d7 <+199>:	jmp    0x5555554015b4 <phase_6+164>
   0x00005555554015d9 <+201>:	mov    0x20(%rsp),%rbx
   0x00005555554015de <+206>:	mov    0x28(%rsp),%rax
   0x00005555554015e3 <+211>:	mov    %rax,0x8(%rbx)
   0x00005555554015e7 <+215>:	mov    0x30(%rsp),%rdx
   0x00005555554015ec <+220>:	mov    %rdx,0x8(%rax)
   0x00005555554015f0 <+224>:	mov    0x38(%rsp),%rax
   0x00005555554015f5 <+229>:	mov    %rax,0x8(%rdx)
   0x00005555554015f9 <+233>:	mov    0x40(%rsp),%rdx
   0x00005555554015fe <+238>:	mov    %rdx,0x8(%rax)
   0x0000555555401602 <+242>:	mov    0x48(%rsp),%rax
   0x0000555555401607 <+247>:	mov    %rax,0x8(%rdx)
   0x000055555540160b <+251>:	movq   $0x0,0x8(%rax)
   0x0000555555401613 <+259>:	mov    $0x5,%ebp
   0x0000555555401618 <+264>:	jmp    0x555555401623 <phase_6+275>
   0x000055555540161a <+266>:	mov    0x8(%rbx),%rbx
   0x000055555540161e <+270>:	sub    $0x1,%ebp
   0x0000555555401621 <+273>:	je     0x555555401634 <phase_6+292>
   0x0000555555401623 <+275>:	mov    0x8(%rbx),%rax
   0x0000555555401627 <+279>:	mov    (%rax),%eax
   0x0000555555401629 <+281>:	cmp    %eax,(%rbx)
   0x000055555540162b <+283>:	jge    0x55555540161a <phase_6+266>
   0x000055555540162d <+285>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401632 <+290>:	jmp    0x55555540161a <phase_6+266>
   0x0000555555401634 <+292>:	mov    0x58(%rsp),%rax
   0x0000555555401639 <+297>:	xor    %fs:0x28,%rax
   0x0000555555401642 <+306>:	jne    0x555555401651 <phase_6+321>
   0x0000555555401644 <+308>:	add    $0x60,%rsp
   0x0000555555401648 <+312>:	pop    %rbx
   0x0000555555401649 <+313>:	pop    %rbp
   0x000055555540164a <+314>:	pop    %r12
   0x000055555540164c <+316>:	pop    %r13
   0x000055555540164e <+318>:	pop    %r14
   0x0000555555401650 <+320>:	retq
   0x0000555555401651 <+321>:	callq  0x555555400f20 <__stack_chk_fail@plt>

<+28 ~ 48>

 

   0x000055555540152c <+28>:	mov    %rsp,%r13
   0x000055555540152f <+31>:	mov    %r13,%rsi
   0x0000555555401532 <+34>:	callq  0x555555401acc <read_six_numbers>
   0x0000555555401537 <+39>:	mov    %r13,%r12
   0x000055555540153a <+42>:	mov    $0x0,%r14d
   0x0000555555401540 <+48>:	jmp    0x555555401567 <phase_6+87>

 

<+28> : %rsp는 입력한 여섯개의 정수 배열을 가리키는 포인터입니다. 이를 %r13에도 저장합니다.

 

<+31> : %rsi에도 이를 저장하지만, 테스트 결과 %rsi에 저장된 값을 read_six_numbers가 리턴되었을 때에는 이미 소멸됨을 확인했습니다.

 

<+34> : %r12에도 저장합니다.

 

<+42> : %r14d에 0을 저장합니다.

 

<+48> : <+87>로 점프합니다.

 


 

 

<+87 ~ 100> 

: %r13, %rbp가 가리키는 숫자가 1 이상 6이하인지 검사

 

   0x0000555555401567 <+87>:	mov    %r13,%rbp
   0x000055555540156a <+90>:	mov    0x0(%r13),%eax
   0x000055555540156e <+94>:	sub    $0x1,%eax
   0x0000555555401571 <+97>:	cmp    $0x5,%eax
   0x0000555555401574 <+100>:	ja     0x555555401542 <phase_6+50>

 

<+87> : %rbp에도 포인터를 넘겨줍니다.

 

 

<+90> : 입력한 첫번째 정수를 %eax에 복사합니다.

 

<+94> : %eax에서 1을 뺍니다.

 

<+97 ~ 100> : 5와 비교해서 5보다 크거나 0보다 작으면 <+50>으로 점프합니다. <+50>은 폭탄이므로 입력하는 첫번째 정수는 1 이상 6이하여야 합니다.

 

   0x0000555555401542 <+50>:	callq  0x555555401a90 <explode_bomb>

<+102 ~ 115>

: %r14d는 Outer Loop의 현재 인덱스(%r13과, %rbp가 가리키는 인덱스와 같음)

 

   0x0000555555401576 <+102>:	add    $0x1,%r14d
   0x000055555540157a <+106>:	cmp    $0x6,%r14d
   0x000055555540157e <+110>:	je     0x555555401585 <phase_6+117>
   0x0000555555401580 <+112>:	mov    %r14d,%ebx
   0x0000555555401583 <+115>:	jmp    0x555555401551 <phase_6+65>

 

<+102> : %r14d에 1을 더합니다.

 

<+106> : %r14d를 6과 비교합니다.

 

<+110> : 6과 같으면 <+117>로 점프합니다. 여기서는 점프하지 않고 넘어갑니다.

 

<+112> : %r14d의 값을 %ebx에 복사합니다.

 

<+115> : <+65>로 점프합니다.

 


<+65 ~ 76>

: %rbp가 가리키는 숫자(Outer Loop의 현재 원소)와 %eax(Inner Loop의 현재 원소)가 다른지 검사.

 

   0x0000555555401551 <+65>:	movslq %ebx,%rax
   0x0000555555401554 <+68>:	mov    (%rsp,%rax,4),%eax
   0x0000555555401557 <+71>:	cmp    %eax,0x0(%rbp)
   0x000055555540155a <+74>:	jne    0x555555401549 <phase_6+57>
   0x000055555540155c <+76>:	callq  0x555555401a90 <explode_bomb>

 

 

<+65> : %rax에 %ebx값을 복사합니다.

 

<+68> : %eax에 %rsp에서 현재 저장된 인덱스만큼 이동한 원소의 값을 저장합니다.

 

<+71> : %eax와 %rbp가 가리키는 값을 비교합니다.

 

<+74> : <+76>에서 폭탄이 터지므로 %eax와 %rbp가 가리키는 값은 달라야합니다.

 

 


 

<+57 ~ 71>

: %ebx는 Inner Loop의 인덱스

 

   0x0000555555401549 <+57>:	add    $0x1,%ebx
   0x000055555540154c <+60>:	cmp    $0x5,%ebx
   0x000055555540154f <+63>:	jg     0x555555401563 <phase_6+83>
   0x0000555555401551 <+65>:	movslq %ebx,%rax
   0x0000555555401554 <+68>:	mov    (%rsp,%rax,4),%eax
   0x0000555555401557 <+71>:	cmp    %eax,0x0(%rbp)
   0x000055555540155a <+74>:	jne    0x555555401549 <phase_6+57>
   0x000055555540155c <+76>:	callq  0x555555401a90 <explode_bomb>

 

<+57> : %ebx에 1을 더합니다.

 

<+60 ~ 63> : %ebx에 6이 되었다면 <+83>로 점프합니다.

 

<+65 ~ 71>을 수행한 후 %ebx가 6이 되기 전까지 반복합니다.


<중간결론1>

 

2중 Loop 구조입니다.

 

<+87 ~ 115> 는 Outer Loop입니다. 여기서는 %rbp, %r13을 1부터 6까지 순회합니다.(<+83>에서 오른쪽으로 한칸씩 옮겨감) 그리고 그 인덱스를 %r14d가 소유합니다.

Outer Loop를 순회하면서 각 원소마다 Inner Loop인 <+57 ~ 71>을 순회합니다.

여기서는 Outer Loop의 숫자와 Inner Loop의 숫자가 같으면 폭탄이 터지도록 합니다.

 

모든 검사가 끝나면 <+110>에서 현재 인덱스가 6인지 검사해서 순회가 끝났으므로 <+117>로 이동해 다음 과정들을 순회하게 됩니다.

 

즉, 모든 숫자는 1 이상 6 이하여야하고, 그리고 그 숫자들은 모두 달라야합니다.

 

마지막 순간에는 다음과 같은 형태가 될겁니다.

 

조건들을 만족했으니 <+117>로 가야합니다.

 


 

<+117 ~ 144, +146 ~ 151>

 

   0x0000555555401585 <+117>:	lea    0x18(%r12),%rcx
   0x000055555540158a <+122>:	mov    $0x7,%edx
   0x000055555540158f <+127>:	mov    %edx,%eax
   0x0000555555401591 <+129>:	sub    (%r12),%eax
   0x0000555555401595 <+133>:	mov    %eax,(%r12)
   0x0000555555401599 <+137>:	add    $0x4,%r12
   0x000055555540159d <+141>:	cmp    %r12,%rcx
   0x00005555554015a0 <+144>:	jne    0x55555540158f <phase_6+127>

<+117> : %rcx가 배열의 마지막 원소의 끝 주소를 가지게 합니다. (맨 마지막의 다음 숫자는 0입니다)

 

<+122 ~ 127> : %eax와 %edx에 7을 복사합니다.

 

<+129 ~ 133> : %r12가 가리키는 값을 a라고 할 때 7-a를 가지도록합니다.

 

<+141 ~ 144> : 만약 원소의 끝 주소에 도달하지 않았다면 <+117 ~ 144>를 반복합니다.

 

결론적으로, 입력한 배열의 숫자를 모두 7에서 뺀 값으로 저장하게합니다.

 

<+146 ~ 151> : 순회를 마치고, <+146>으로 진입한 후에 %esi에 0을 저장하고 <+179>로 점프합니다.

 

현재 상황은 밑과 같습니다.


 

<+179 ~ 197>

 

<+179>:	mov    (%rsp,%rsi,4),%ecx
<+182>:	mov    $0x1,%eax
<+187>:	lea    0x202c5e(%rip),%rdx        # 0x555555604230 <node1>
<+194>:	cmp    $0x1,%ecx
<+197>:	jg     0x5555554015a9 <phase_6+153>
<+199>:	jmp    0x5555554015b4 <phase_6+164>

 

<+179> : %ecx는 R[%rsi]번째 숫자가 됩니다. 지금은 0번째 인덱스 숫자인 6을 저장합니다.

 

<+182> : %eax에 1을 저장합니다.

 

<+187> : 우선은 %rdx가 0x555555604230 주소값이 저장된다고 까지만 생각하고 넘어가겠습니다.

 

<+194 ~ 197> : R[%ecx]와 1을 비교해서 1 초과라면 <+153>로 넘어가고, 1 이면 <+164>로 넘어갑니다.


 

<+153 ~ 162>

: %rdx가 node 배열의 인덱스 (R[%ecx] - 1)의 값이 가리키는 대상을 가지게 된다.

 

   0x00005555554015a9 <+153>:	mov    0x8(%rdx),%rdx
   0x00005555554015ad <+157>:	add    $0x1,%eax
   0x00005555554015b0 <+160>:	cmp    %ecx,%eax
   0x00005555554015b2 <+162>:	jne    0x5555554015a9 <phase_6+153>
(gdb) x/20xg 0x555555604230
0x555555604230 <node1>:	0x00000001000003aa	0x0000555555604240
0x555555604240 <node2>:	0x0000000200000206	0x0000555555604250
0x555555604250 <node3>:	0x0000000300000076	0x0000555555604260
0x555555604260 <node4>:	0x000000040000039c	0x0000555555604270
0x555555604270 <node5>:	0x0000000500000277	0x0000555555604110
0x555555604280 <host_table>:	0x0000555555402e47	0x0000555555402e61
0x555555604290 <host_table+16>:	0x0000555555402e7b	0x0000555555402e94
0x5555556042a0 <host_table+32>:	0x0000555555402eae	0x0000555555402ebc
0x5555556042b0 <host_table+48>:	0x0000555555402ecb	0x0000000000000000
0x5555556042c0 <host_table+64>:	0x0000000000000000	0x0000000000000000

<+153> : %rdx의 참조를 8바이트 씩 이동해서 복사하므로 보기편하도록 8바이트 배열 관점에서 관찰했습니다. 그리고 %rdx를 배열에서 한칸 옮긴 값의 참조를 저장합니다. 또한 앞으로 저 배열을 node 배열이라고 하겠습니다.

 

node 배열에서 한번 이동 : M[0x555555604230 + 0x8] 이 저장된다. 즉 0x555555604240가 저장된다.

0x555555604240 <node2>:	0x0000000200000206	0x0000555555604250

 

또한, 지금 서로의 주소값을 지니고 있는데 잘 관찰해보면 node배열은 다음과 같은 구조를 가집니다.

&node a는 노드 a의 주소값입니다.

 

 

<+157> : %eax에 1을 더합니다.

 

<+160 ~ 162> : %ecx는 R[%rsi]번째 숫자입니다. 이를 %eax와 비교해서 같을때까지 <+153 ~ 162>를 반복합니다.

 

%rsi는 0으로 고정입니다. 즉 %ecx는 0번째 숫자입니다.

또한 %eax가 1부터 %ecx가 될때까지 반복하므로, %ecx번 반복한다고 생각할 수 있고.

한번 더 나아가서, %rdx를 node 배열에서 %ecx번 이동를 반복하게 합니다.

 

예를들어, node1에서 5번 이동하면 %rdx는 node6 주소를 값으로 가집니다.

 

마지막의 상태는 다음과 같습니다.


 

 

<+164 ~ 177>

: 8바이트 단위로 옮겨서 배열이 저장되는 위치에서 0x20 떨어진곳에  %rdx가 가리키는값 저장.

 

   0x00005555554015b4 <+164>:	mov    %rdx,0x20(%rsp,%rsi,8)
   0x00005555554015b9 <+169>:	add    $0x1,%rsi
   0x00005555554015bd <+173>:	cmp    $0x6,%rsi
   0x00005555554015c1 <+177>:	je     0x5555554015d9 <phase_6+201>

 

<+164> : 우리가 입력한 배열이 저장되는 위치에서 0x20 떨어진곳에 8바이트 단위로 옮겨서 %rdx가 node 배열에 가리키는 값을 저장합니다.

 

<+169 ~ 177> : %rsi가 6이 되었다면 <+201>로 점프하고, 아니라면 <+179>에서 진행합니다.


 

<중간결론2>

 

이번에도 2중 Loop 구조입니다.

 

<+179 ~ 197> Outer Loop, <+153 ~ 177> Inner Loop 입니다.

R[%ecx]가 1이면 %rdx가 가리키는 위치를 옮길 필요가 없기때문에 <+153 ~ 162>를 진행하지 않는것 뿐이고,

결론적으로 %rdx가 가리키는 index (R[%ecx] - 1) 값을 우리가 저장한 배열 오른쪽에 저장해놓는겁니다.

 

R[%rsi] 가 가리키는 인덱스가 현재 진행하는 Outer Loop의 index입니다.

%ecx는 R[%rsi]번째 숫자만큼 이동해서 가지는 값을 0x20 + R[%rsp]부터 8바이트씩 옮겨 복사합니다.

 

즉, 1 2 3 4 5 6을 입력한 우리의 경우 배열에는 6 5 4 3 2 1( = 7-6, 7-5, 7-4, 7-3, 7-2, 7-1)이 저장되었고

그 오른쪽 주소에 %rdx가 가리키는 node배열의 node 6, node 5, node 4, node 3, node 2, node 1 의 값들이 차례대로 저장되는겁니다.

사진을 보면 다음과 같게 됩니다.

 

(배열의 주소는 보기 편하도록 한것이지, 실제 주소가 아닙니다)

 

즉, R[%rsi]가 6이 되어 Outer Loop까지 종료되었을때,

<+201> 부터 코드가 진행됩니다.


 

<+201  ~ 206> 

 

   0x00005555554015d9 <+201>:	mov    0x20(%rsp),%rbx
   0x00005555554015de <+206>:	mov    0x28(%rsp),%rax
   0x00005555554015e3 <+211>:	mov    %rax,0x8(%rbx)
   0x00005555554015e7 <+215>:	mov    0x30(%rsp),%rdx
   0x00005555554015ec <+220>:	mov    %rdx,0x8(%rax)
   0x00005555554015f0 <+224>:	mov    0x38(%rsp),%rax
   0x00005555554015f5 <+229>:	mov    %rax,0x8(%rdx)
   0x00005555554015f9 <+233>:	mov    0x40(%rsp),%rdx
   0x00005555554015fe <+238>:	mov    %rdx,0x8(%rax)
   0x0000555555401602 <+242>:	mov    0x48(%rsp),%rax
   0x0000555555401607 <+247>:	mov    %rax,0x8(%rdx)
   0x000055555540160b <+251>:	movq   $0x0,0x8(%rax)
   0x0000555555401613 <+259>:	mov    $0x5,%ebp
   0x0000555555401618 <+264>:	jmp    0x555555401623 <phase_6+275>

 

 

결론적으로 보면 기존에 오른쪽같이 연결되어있던 노드들을

위에서 0x20(%rsp)에 저장되어 있는 순서대로 재배치하는 코드입니다.

 

여기서부터는 6 3 1 2 5 4 을 입력했다고 가정하고 진행해보겠습니다.

6 3 1 2 5 4을 입력한 숫자들은 1 4 6 5 2 3 으로 변환되고, 0x20(%rsp)에 node1, node4, node6, node5, node2, node3의 주소가 배치되게 됩니다.

 

그리고 <+201  ~ 206> 을 진행하고 나면, 노드사이 관계가 오른쪽과 같이 변경됩니다.

 

변경이 완료되면 <+275>로 점프합니다.

 

 


 

<+266 ~ 285>

 

   0x000055555540161a <+266>:	mov    0x8(%rbx),%rbx
   0x000055555540161e <+270>:	sub    $0x1,%ebp
   0x0000555555401621 <+273>:	je     0x555555401634 <phase_6+292>
   0x0000555555401623 <+275>:	mov    0x8(%rbx),%rax
   0x0000555555401627 <+279>:	mov    (%rax),%eax
   0x0000555555401629 <+281>:	cmp    %eax,(%rbx)
   0x000055555540162b <+283>:	jge    0x55555540161a <phase_6+266>
   0x000055555540162d <+285>:	callq  0x555555401a90 <explode_bomb>

<+275> : %rax는 %rbx의 다음 노드 주소를 가집니다. 현재 %rbx는 첫번째 노드 주소이므로, %rax는 두번째 노드 주소입니다.

즉 R[%rbx]는 &node1, R[%rax]는 &node4 입니다.

 

<+279> : %eax는 두번째 노드인데, 그 앞의 8바이트만 가져오므로 v4를 저장합니다.

 

<+281 ~ 283> : 두번째 노드의 값인 v4와 첫번째 노드 값인 v1을 비교합니다. 그리고 v1 >= v4를 만족해야합니다. 그리고 <+266>으로 점프합니다, 아니라면 폭탄이 터집니다.

 

 

<+266> : %rbx를 다음 노드로 옮깁니다.

 

<+270 ~ 273> : %ebp를 1 감소합니다. 만약 %ebp가 0보다 작다면 <+292>로 이동합니다.

 

이를 반복하는데, 결국 %ebp가 5에서 1까지 반복하므로 총 5번 반복합니다. 그리고 %rbx를 다음 노드로 옮기면서 %eax는 %rbx의 다음 노드가 됩니다. 또한 그때마다 앞에놓인 노드의 v값이 뒤에놓인 노드의 v값보다 크거나 같아야합니다.

v값을 기준으로 내림차순을 만족해야합니다.


<결론>

 

각 노드의 v값을 나열해보면 다음과 같습니다.

 

(gdb) x/30dw 0x555555604230
0x555555604230 <node1>:	938	1	1432371776	21845
0x555555604240 <node2>:	518	2	1432371792	21845
0x555555604250 <node3>:	118	3	1432371808	21845
0x555555604260 <node4>:	924	4	1432371824	21845
0x555555604270 <node5>:	631	5	1432371472	21845

(gdb) x/1dw 0x0000555555604110
0x555555604110 <node6>:	891

 

node v node
1 938
2 518
3 118
4 924
5 631
6 891

 

노드가 큰 순서대로 나열하면

1 → 4 → 6 → 5 →2 →3

입니다.

 

그리고 이 노드들은 7에서 입력한 숫자를 뺀 값이므로,

6 3 1 2 5 4

로 변환됩니다.

 

즉 답은 다음과 같습니다.

6 3 1 2 5 4

 

 

 

'CS:APP' 카테고리의 다른 글

Pipelining의 성능 측정과 비교  (0) 2023.12.04
[CS:APP 2.2] Binary Number Encoding  (0) 2023.10.26
[CS:APP] BombLab Phase5 해설  (0) 2023.10.20
[CS:APP] BombLab Phase4 해설  (0) 2023.10.20
[CS:APP] BombLab Phase3 해설  (0) 2023.10.20

phase_5

Dump of assembler code for function phase_5:
=> 0x00005555554014ca <+0>:	push   %rbx
   0x00005555554014cb <+1>:	mov    %rdi,%rbx
   0x00005555554014ce <+4>:	callq  0x55555540176f <string_length>
   0x00005555554014d3 <+9>:	cmp    $0x6,%eax
   0x00005555554014d6 <+12>:	jne    0x555555401509 <phase_5+63>
   0x00005555554014d8 <+14>:	mov    %rbx,%rax
   0x00005555554014db <+17>:	lea    0x6(%rbx),%rdi
   0x00005555554014df <+21>:	mov    $0x0,%ecx
   0x00005555554014e4 <+26>:	lea    0x1675(%rip),%rsi        # 0x555555402b60 <array.3417>
   0x00005555554014eb <+33>:	movzbl (%rax),%edx
   0x00005555554014ee <+36>:	and    $0xf,%edx
   0x00005555554014f1 <+39>:	add    (%rsi,%rdx,4),%ecx
   0x00005555554014f4 <+42>:	add    $0x1,%rax
   0x00005555554014f8 <+46>:	cmp    %rdi,%rax
   0x00005555554014fb <+49>:	jne    0x5555554014eb <phase_5+33>
   0x00005555554014fd <+51>:	cmp    $0x3a,%ecx
   0x0000555555401500 <+54>:	je     0x555555401507 <phase_5+61>
   0x0000555555401502 <+56>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401507 <+61>:	pop    %rbx
   0x0000555555401508 <+62>:	retq
   0x0000555555401509 <+63>:	callq  0x555555401a90 <explode_bomb>
   0x000055555540150e <+68>:	jmp    0x5555554014d8 <phase_5+14>

<+1 ~ 12>

 

   0x00005555554014cb <+1>:	mov    %rdi,%rbx
   0x00005555554014ce <+4>:	callq  0x55555540176f <string_length>
   0x00005555554014d3 <+9>:	cmp    $0x6,%eax
   0x00005555554014d6 <+12>:	jne    0x555555401509 <phase_5+63>

<+1> : 전에 분석했듯이 우리가 입력한 것들은 한줄의 문자열로 %rdi에 저장됩니다. 그리고 이를 %rbx에도 저장합니다.

 

<+4> : string_length도 phase_1에서 분석했었듯이 레지스터 %rdi와 %rdx로 이루어져 그 간격을 %rax에 저장해서 반환하는 함수입니다.

 

<+9> : 그 반환값을 6과 비교합니다.

 

<+12> : 반환값, 즉 문자열의 길이가 6이 아니라면 <+63>으로 점프해서 폭탄이 터지므로, 입력하는 문자열의 길이는 6이어야합니다.

   0x0000555555401509 <+63>:	callq  0x555555401a90 <explode_bomb>

<+14 ~ 36>

 

   0x00005555554014d8 <+14>:	mov    %rbx,%rax
   0x00005555554014db <+17>:	lea    0x6(%rbx),%rdi
   0x00005555554014df <+21>:	mov    $0x0,%ecx
   0x00005555554014e4 <+26>:	lea    0x1675(%rip),%rsi        # 0x555555402b60 <array.3417>
   0x00005555554014eb <+33>:	movzbl (%rax),%edx
   0x00005555554014ee <+36>:	and    $0xf,%edx

 

<+14> : %rax에 %rbx가 가지고 있는 포인터를 복사합니다.

 

<+17> : %rdi에 0x6(%rbx), 즉 6번째 문자를 저장합니다.

 

<+21> : %ecx에 0을 저장합니다.

 

<+26> : 배열을 저장하는것으로 추측됩니다.

 

<+33> : (%rax)는 문자열의 첫번째 문자입니다. 이를 %edx에 저장합니다.

 

<+36> : 첫번째 문자를 15와 &연산합니다. 영어 문자가 대소문자든 숫자문자든 아무상관없이 정수 1부터 26까지 순서대로 대응되도록 해줍니다.

 

 

a b c d e f g h i j k l
A B C D E F G H I J K L
'1' '2' '3' '4' '5' '6' '7' '8' '9'      
1 2 3 4 5 6 7 8 9 10 11 12

 

m n o p q r s t u v w x y z
M N O P Q R S T U V W X Y Z
13 14 15 16 17 18 19 20 21 22 23 24 25 26

 

 


<+39 ~ 68>

   0x00005555554014f1 <+39>:	add    (%rsi,%rdx,4),%ecx
   0x00005555554014f4 <+42>:	add    $0x1,%rax
   0x00005555554014f8 <+46>:	cmp    %rdi,%rax
   0x00005555554014fb <+49>:	jne    0x5555554014eb <phase_5+33>
   0x00005555554014fd <+51>:	cmp    $0x3a,%ecx
   0x0000555555401500 <+54>:	je     0x555555401507 <phase_5+61>
   0x0000555555401502 <+56>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401507 <+61>:	pop    %rbx
   0x0000555555401508 <+62>:	retq
   0x0000555555401509 <+63>:	callq  0x555555401a90 <explode_bomb>
   0x000055555540150e <+68>:	jmp    0x5555554014d8 <phase_5+14>

 

<+39> : %rsi에 정보를 알기 힘든 배열을 저장했었습니다. 그리고 이 <+39>의 operand에서 %rsi의 배열을 4바이트를 단위로 접근함을 알 수 있습니다. 즉 %rsi는 4바이트 int형 정수배열이 저장되었다는 관점으로 볼 수 있습니다.

 

(gdb) x/30dw $rsi
0x555555402b60 <array.3417>:	2	10	6	1
0x555555402b70 <array.3417+16>:	12	16	9	3
0x555555402b80 <array.3417+32>:	4	7	14	5
0x555555402b90 <array.3417+48>:	11	8	15	13
0x555555402ba0:	2032168787	1948284271	1802398056	1970239776
0x555555402bb0:	1851876128	1869902624	1752440944	1868701797
0x555555402bc0:	1998611053	543716457	1819440227	539779885
0x555555402bd0:	2032168804	4158831

 

때문에, 4바이트 단위로 끊어서 메모리에 저장된 값을 출력하면 다음과 같습니다.

 

30개를 출력해보았더니, 16개까지는 인위적으로 값을 집어넣은듯한 값이 있고 그 이후로는 쓰레기 값이 있기때문에 우선 사용할것같은 16개까지만 적었습니다. 

 

 

그리고 이 배열의 입력한 첫번째 문자에서 15와 &연산한 값만큼 한칸씩 이동해서 나온 element를 %ecx에 저장합니다.

저는 "abcdef"를 입력했다고 가정했기 때문에, 첫번째 element인 2를 저장합니다.

 

<+42> : %rax에 1을 더합니다.

 

<+46> :  %rax와 %rdi를 비교합니다.

 

<+49> : %rax와 %rdi가 다르면 <+33>으로 점프합니다.

 

<+51> : %rax와 %rdi가 같으면 58과 %ecx를 비교합니다.

 

<+54> : 58이면 <+61>로 점프해서 문제가 해결됩니다.

 


<결론>

상황을 관찰해보면,

%rax는 배열을 첫번째 요소에서 마지막 요소까지 순회합니다.

순회하면서, 각 입력한 문자가 위의 정수 배열의 인덱스 역할을 하며 인덱스가 가리키는 정수를 %ecx에 누적합니다.

 

그리고 마지막 요소의 순회까지 끝나면 R[%ecx]가 58이 되어야 합니다.

즉, 우리가 입력해야하는 문자열은 각 문자가 1부터 9로 대응되며 그 합이 58이 되는 문자열이고 답은 너무나도 많습니다.

 

몇가지 예시만 들어보면,

우선 58을 위의 배열에 있는 숫자 중에서 6개 숫자의 합으로 분리해야하므로,

10 + 10 + 10 + 16 + 9 + 3 으로 분리할 수 있습니다.

각 숫자의 인덱스는 1 1 1 5 6 7 이고, 1 1 1 5 6 7로 대응되는 문자들은 아래를 포함하여 너무나 많은 조합이 가능합니다.

aaaefg, 111567, AAAEFG, a1156G

 

'CS:APP' 카테고리의 다른 글

[CS:APP 2.2] Binary Number Encoding  (0) 2023.10.26
[CS:APP] BombLab Phase6 해설  (0) 2023.10.20
[CS:APP] BombLab Phase4 해설  (0) 2023.10.20
[CS:APP] BombLab Phase3 해설  (0) 2023.10.20
[CS:APP] BombLab Phase2 해설  (0) 2023.10.19

+ Recent posts